Step1 of source move.
This commit is contained in:
parent
aa786ec04a
commit
ffa8b0bf4e
69 changed files with 15640 additions and 0 deletions
775
src/actions.cpp
Normal file
775
src/actions.cpp
Normal file
|
@ -0,0 +1,775 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "actions.hpp"
|
||||
#include "display.hpp"
|
||||
#include "game_config.hpp"
|
||||
#include "game_events.hpp"
|
||||
#include "key.hpp"
|
||||
#include "language.hpp"
|
||||
#include "map.hpp"
|
||||
#include "pathfind.hpp"
|
||||
#include "replay.hpp"
|
||||
#include "sound.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
|
||||
struct castle_cost_calculator
|
||||
{
|
||||
castle_cost_calculator(const gamemap& map) : map_(map)
|
||||
{}
|
||||
|
||||
double cost(const gamemap::location& loc) const
|
||||
{
|
||||
if(!map_.on_board(loc) || map_[loc.x][loc.y] != gamemap::CASTLE)
|
||||
return 10000;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private:
|
||||
const gamemap& map_;
|
||||
};
|
||||
|
||||
std::string recruit_unit(const gamemap& map, int side,
|
||||
std::map<gamemap::location,unit>& units, unit& new_unit,
|
||||
gamemap::location recruit_location, display* disp)
|
||||
{
|
||||
typedef std::map<gamemap::location,unit> units_map;
|
||||
|
||||
//find the unit that can recruit
|
||||
units_map::const_iterator u;
|
||||
|
||||
for(u = units.begin(); u != units.end(); ++u) {
|
||||
if(u->second.can_recruit() && u->second.side() == side) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(u == units.end())
|
||||
return string_table["no_leader_to_recruit"];
|
||||
|
||||
if(!map.is_starting_position(u->first))
|
||||
return string_table["leader_not_on_start"];
|
||||
|
||||
if(map.on_board(recruit_location)) {
|
||||
const paths::route& rt = a_star_search(u->first,recruit_location,
|
||||
100.0,castle_cost_calculator(map));
|
||||
if(rt.steps.empty() || units.find(recruit_location) != units.end() ||
|
||||
map[recruit_location.x][recruit_location.y] != gamemap::CASTLE)
|
||||
recruit_location = gamemap::location();
|
||||
}
|
||||
|
||||
if(!map.on_board(recruit_location)) {
|
||||
recruit_location = find_vacant_tile(map,units,u->first,gamemap::CASTLE);
|
||||
}
|
||||
|
||||
if(!map.on_board(recruit_location)) {
|
||||
return string_table["no_recruit_location"];
|
||||
}
|
||||
|
||||
new_unit.set_movement(0);
|
||||
new_unit.set_attacked();
|
||||
units.insert(std::pair<gamemap::location,unit>(
|
||||
recruit_location,new_unit));
|
||||
|
||||
if(disp != NULL && !disp->turbo()) {
|
||||
disp->draw(true,true);
|
||||
|
||||
for(double alpha = 0.0; alpha <= 1.0; alpha += 0.1) {
|
||||
disp->draw_tile(recruit_location.x,recruit_location.y,NULL,alpha);
|
||||
disp->update_display();
|
||||
SDL_Delay(20);
|
||||
}
|
||||
}
|
||||
|
||||
return std::string();
|
||||
}
|
||||
|
||||
bool under_leadership(const std::map<gamemap::location,unit>& units,
|
||||
const gamemap::location& loc)
|
||||
{
|
||||
gamemap::location adjacent[6];
|
||||
get_adjacent_tiles(loc,adjacent);
|
||||
const std::map<gamemap::location,unit>::const_iterator un =
|
||||
units.find(loc);
|
||||
if(un == units.end())
|
||||
return false;
|
||||
|
||||
const int side = un->second.side();
|
||||
const int level = un->second.type().level();
|
||||
|
||||
for(int i = 0; i != 6; ++i) {
|
||||
const std::map<gamemap::location,unit>::const_iterator it =
|
||||
units.find(adjacent[i]);
|
||||
if(it != units.end() && it->second.side() == side &&
|
||||
it->second.type().is_leader() && it->second.type().level() > level)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
battle_stats evaluate_battle_stats(
|
||||
const gamemap& map,
|
||||
const gamemap::location& attacker,
|
||||
const gamemap::location& defender,
|
||||
int attack_with,
|
||||
std::map<gamemap::location,unit>& units,
|
||||
const gamestatus& state,
|
||||
const game_data& info,
|
||||
gamemap::TERRAIN attacker_terrain_override,
|
||||
bool include_strings)
|
||||
{
|
||||
battle_stats res;
|
||||
|
||||
res.attack_with = attack_with;
|
||||
|
||||
if(include_strings)
|
||||
res.defend_name = "none";
|
||||
|
||||
const std::map<gamemap::location,unit>::iterator a = units.find(attacker);
|
||||
const std::map<gamemap::location,unit>::iterator d = units.find(defender);
|
||||
|
||||
assert(a != units.end());
|
||||
assert(d != units.end());
|
||||
|
||||
const gamemap::TERRAIN attacker_terrain = attacker_terrain_override ?
|
||||
attacker_terrain_override : map[attacker.x][attacker.y];
|
||||
const gamemap::TERRAIN defender_terrain = map[defender.x][defender.y];
|
||||
|
||||
res.chance_to_hit_attacker =
|
||||
a->second.defense_modifier(map,attacker_terrain);
|
||||
|
||||
res.chance_to_hit_defender =
|
||||
d->second.defense_modifier(map,defender_terrain);
|
||||
|
||||
const std::vector<attack_type>& attacker_attacks =
|
||||
a->second.attacks();
|
||||
const std::vector<attack_type>& defender_attacks =
|
||||
d->second.attacks();
|
||||
|
||||
assert(attack_with >= 0 && attack_with < int(attacker_attacks.size()));
|
||||
const attack_type& attack = attacker_attacks[attack_with];
|
||||
|
||||
static const std::string charge_string("charge");
|
||||
const bool charge = (attack.special() == charge_string);
|
||||
|
||||
bool backstab = false;
|
||||
|
||||
static const std::string backstab_string("backstab");
|
||||
if(attack.special() == backstab_string) {
|
||||
gamemap::location adj[6];
|
||||
get_adjacent_tiles(defender,adj);
|
||||
int i;
|
||||
for(i = 0; i != 6; ++i) {
|
||||
if(adj[i] == attacker)
|
||||
break;
|
||||
}
|
||||
|
||||
if(i != 6) {
|
||||
const std::map<gamemap::location,unit>::const_iterator u =
|
||||
units.find(adj[(i+3)%6]);
|
||||
if(u != units.end() && u->second.side() == a->second.side()) {
|
||||
backstab = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const std::string plague_string("plague");
|
||||
res.attacker_plague = (attack.special() == plague_string);
|
||||
res.defender_plague = false;
|
||||
|
||||
res.attack_name = attack.name();
|
||||
res.attack_type = attack.type();
|
||||
|
||||
if(include_strings) {
|
||||
res.attack_special = attack.special();
|
||||
|
||||
//don't show backstabbing unless it's actually happening
|
||||
if(res.attack_special == "backstab" && !backstab)
|
||||
res.attack_special = "";
|
||||
|
||||
res.range = (attack.range() == attack_type::SHORT_RANGE ?
|
||||
"Melee" : "Ranged");
|
||||
}
|
||||
|
||||
res.nattacks = attack.num_attacks();
|
||||
int defend;
|
||||
res.ndefends = 0;
|
||||
for(defend = 0; defend != int(defender_attacks.size()); ++defend) {
|
||||
if(defender_attacks[defend].range() == attack.range())
|
||||
break;
|
||||
}
|
||||
|
||||
res.defend_with = defend != int(defender_attacks.size()) ? defend : -1;
|
||||
|
||||
const bool counterattack = defend != int(defender_attacks.size());
|
||||
|
||||
static const std::string drain_string("drain");
|
||||
static const std::string magical_string("magical");
|
||||
|
||||
res.damage_attacker_takes = 0;
|
||||
if(counterattack) {
|
||||
//magical attacks always have a 70% chance to hit
|
||||
if(defender_attacks[defend].special() == magical_string)
|
||||
res.chance_to_hit_attacker = 0.7;
|
||||
|
||||
res.damage_attacker_takes = int(double(
|
||||
a->second.damage_against(defender_attacks[defend]))
|
||||
* combat_modifier(state,units,d->first,d->second.type().alignment()));
|
||||
|
||||
if(charge)
|
||||
res.damage_attacker_takes *= 2;
|
||||
|
||||
if(under_leadership(units,defender))
|
||||
res.damage_attacker_takes += res.damage_attacker_takes/8 + 1;
|
||||
|
||||
if(res.damage_attacker_takes < 1)
|
||||
res.damage_attacker_takes = 1;
|
||||
|
||||
res.ndefends = defender_attacks[defend].num_attacks();
|
||||
|
||||
res.defend_name = defender_attacks[defend].name();
|
||||
res.defend_type = defender_attacks[defend].type();
|
||||
|
||||
if(include_strings) {
|
||||
res.defend_special = defender_attacks[defend].special();
|
||||
}
|
||||
|
||||
if(defender_attacks[defend].special() == drain_string) {
|
||||
res.amount_defender_drains = res.damage_attacker_takes/2;
|
||||
} else {
|
||||
res.amount_defender_drains = 0;
|
||||
}
|
||||
|
||||
res.defender_plague =
|
||||
(defender_attacks[defend].special() == plague_string);
|
||||
}
|
||||
|
||||
if(attack.special() == magical_string)
|
||||
res.chance_to_hit_defender = 0.7;
|
||||
|
||||
static const std::string marksman_string("marksman");
|
||||
|
||||
//offensive marksman attacks always have at least 60% chance to hit
|
||||
if(res.chance_to_hit_defender < 0.6 && attack.special() == marksman_string)
|
||||
res.chance_to_hit_defender = 0.6;
|
||||
|
||||
res.damage_defender_takes = int(
|
||||
double(d->second.damage_against(attack))
|
||||
* combat_modifier(state,units,a->first,a->second.type().alignment()))
|
||||
* (charge ? 2 : 1) * (backstab ? 2 : 1);
|
||||
|
||||
if(under_leadership(units,attacker))
|
||||
res.damage_defender_takes += res.damage_defender_takes/8 + 1;
|
||||
|
||||
if(attack.special() == drain_string) {
|
||||
res.amount_attacker_drains = res.damage_defender_takes/2;
|
||||
} else {
|
||||
res.amount_attacker_drains = 0;
|
||||
}
|
||||
|
||||
static const std::string slowed_string("slowed");
|
||||
if(a->second.has_flag(slowed_string) && res.nattacks > 1)
|
||||
--res.nattacks;
|
||||
|
||||
if(d->second.has_flag(slowed_string) && res.ndefends > 1)
|
||||
--res.ndefends;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void attack(display& gui, const gamemap& map,
|
||||
const gamemap::location& attacker,
|
||||
const gamemap::location& defender,
|
||||
int attack_with,
|
||||
std::map<gamemap::location,unit>& units,
|
||||
const gamestatus& state,
|
||||
const game_data& info, bool player_is_attacker)
|
||||
{
|
||||
std::map<gamemap::location,unit>::iterator a = units.find(attacker);
|
||||
std::map<gamemap::location,unit>::iterator d = units.find(defender);
|
||||
|
||||
assert(a != units.end());
|
||||
assert(d != units.end());
|
||||
|
||||
int attackerxp = d->second.type().level();
|
||||
int defenderxp = a->second.type().level();
|
||||
|
||||
a->second.set_attacked();
|
||||
|
||||
//if the attacker was invisible, she isn't anymore!
|
||||
static const std::string forest_invisible("ambush");
|
||||
a->second.remove_flag(forest_invisible);
|
||||
|
||||
battle_stats stats = evaluate_battle_stats(map,attacker,defender,
|
||||
attack_with,units,state,info);
|
||||
|
||||
while(stats.nattacks > 0 || stats.ndefends > 0) {
|
||||
if(stats.nattacks > 0) {
|
||||
const double roll = double(recorder.get_random()%1000)/1000.0;
|
||||
const bool hits = roll < stats.chance_to_hit_defender;
|
||||
const bool dies = gui.unit_attack(attacker,defender,
|
||||
hits ? stats.damage_defender_takes : 0,
|
||||
a->second.attacks()[attack_with]);
|
||||
if(dies) {
|
||||
attackerxp = 10*d->second.type().level();
|
||||
if(d->second.type().level() == 0)
|
||||
attackerxp = 5;
|
||||
|
||||
defenderxp = 0;
|
||||
|
||||
gamemap::location loc = d->first;
|
||||
gamemap::location attacker_loc = a->first;
|
||||
game_events::fire("die",loc,a->first);
|
||||
|
||||
//the handling of the event may have removed the object
|
||||
//so we have to find it again
|
||||
units.erase(loc);
|
||||
|
||||
//plague units make clones of themselves on the target hex
|
||||
//units on villages that die cannot be plagued
|
||||
if(stats.attacker_plague && map[loc.x][loc.y]!=gamemap::TOWER) {
|
||||
a = units.find(attacker_loc);
|
||||
if(a != units.end()) {
|
||||
units.insert(std::pair<gamemap::location,unit>(
|
||||
loc,a->second));
|
||||
gui.draw_tile(loc.x,loc.y);
|
||||
gui.update_display();
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
} else if(hits) {
|
||||
static const std::string poison_string("poison");
|
||||
if(stats.attack_special == poison_string &&
|
||||
d->second.has_flag("poisoned") == false) {
|
||||
d->second.set_flag("poisoned");
|
||||
}
|
||||
|
||||
static const std::string slow_string("slow");
|
||||
if(stats.attack_special == slow_string &&
|
||||
d->second.has_flag("slowed") == false) {
|
||||
d->second.set_flag("slowed");
|
||||
if(stats.ndefends > 1)
|
||||
--stats.ndefends;
|
||||
}
|
||||
|
||||
if(stats.amount_attacker_drains > 0) {
|
||||
a->second.gets_hit(-stats.amount_attacker_drains);
|
||||
}
|
||||
}
|
||||
|
||||
--stats.nattacks;
|
||||
}
|
||||
|
||||
if(stats.ndefends > 0) {
|
||||
const double roll = double(recorder.get_random()%1000)/1000.0;
|
||||
const bool hits = roll < stats.chance_to_hit_attacker;
|
||||
const bool dies = gui.unit_attack(defender,attacker,
|
||||
hits ? stats.damage_attacker_takes : 0,
|
||||
d->second.attacks()[stats.defend_with]);
|
||||
|
||||
if(dies) {
|
||||
defenderxp = 10*a->second.type().level();
|
||||
if(a->second.type().level() == 0)
|
||||
defenderxp = 5;
|
||||
|
||||
attackerxp = 0;
|
||||
|
||||
gamemap::location loc = a->first;
|
||||
gamemap::location defender_loc = d->first;
|
||||
game_events::fire("die",loc,d->first);
|
||||
|
||||
//the handling of the event may have removed the object
|
||||
//so we have to find it again
|
||||
units.erase(loc);
|
||||
|
||||
//plague units make clones of themselves on the target hex.
|
||||
//units on villages that die cannot be plagued
|
||||
if(stats.defender_plague && map[loc.x][loc.y]!=gamemap::TOWER) {
|
||||
d = units.find(defender_loc);
|
||||
if(d != units.end()) {
|
||||
units.insert(std::pair<gamemap::location,unit>(
|
||||
loc,d->second));
|
||||
gui.draw_tile(loc.x,loc.y);
|
||||
gui.update_display();
|
||||
}
|
||||
}
|
||||
break;
|
||||
} else if(hits) {
|
||||
if(stats.defend_special == "poison" &&
|
||||
a->second.has_flag("poisoned") == false) {
|
||||
a->second.set_flag("poisoned");
|
||||
}
|
||||
|
||||
static const std::string slow_string("slow");
|
||||
if(stats.defend_special == slow_string &&
|
||||
a->second.has_flag("slowed") == false) {
|
||||
a->second.set_flag("slowed");
|
||||
if(stats.nattacks > 1)
|
||||
--stats.nattacks;
|
||||
}
|
||||
|
||||
if(stats.amount_defender_drains > 0) {
|
||||
d->second.gets_hit(-stats.amount_defender_drains);
|
||||
}
|
||||
}
|
||||
|
||||
--stats.ndefends;
|
||||
}
|
||||
}
|
||||
|
||||
if(attackerxp) {
|
||||
a->second.get_experience(attackerxp);
|
||||
}
|
||||
|
||||
if(defenderxp) {
|
||||
d->second.get_experience(defenderxp);
|
||||
}
|
||||
}
|
||||
|
||||
int tower_owner(const gamemap::location& loc, std::vector<team>& teams)
|
||||
{
|
||||
for(int i = 0; i != teams.size(); ++i) {
|
||||
if(teams[i].owns_tower(loc))
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
void get_tower(const gamemap::location& loc, std::vector<team>& teams,
|
||||
int team_num)
|
||||
{
|
||||
for(int i = 0; i != teams.size(); ++i) {
|
||||
if(i != team_num && teams[i].owns_tower(loc)) {
|
||||
teams[i].lose_tower(loc);
|
||||
}
|
||||
}
|
||||
|
||||
if(team_num >= 0 && team_num < teams.size())
|
||||
teams[team_num].get_tower(loc);
|
||||
}
|
||||
|
||||
std::map<gamemap::location,unit>::iterator
|
||||
find_leader(std::map<gamemap::location,unit>& units, int side)
|
||||
{
|
||||
for(std::map<gamemap::location,unit>::iterator i = units.begin();
|
||||
i != units.end(); ++i) {
|
||||
if(i->second.side() == side && i->second.can_recruit())
|
||||
return i;
|
||||
}
|
||||
|
||||
return units.end();
|
||||
}
|
||||
|
||||
void calculate_healing(display& disp, const gamemap& map,
|
||||
std::map<gamemap::location,unit>& units, int side)
|
||||
{
|
||||
std::map<gamemap::location,int> healed_units, max_healing;
|
||||
|
||||
std::map<gamemap::location,unit>::iterator i;
|
||||
for(i = units.begin(); i != units.end(); ++i) {
|
||||
|
||||
//the unit heals if it's on this side, and if it's on a tower or
|
||||
//it has regeneration
|
||||
if(i->second.side() == side &&
|
||||
(map[i->first.x][i->first.y] == gamemap::TOWER ||
|
||||
i->second.type().regenerates())) {
|
||||
healed_units.insert(std::pair<gamemap::location,int>(
|
||||
i->first, game_config::cure_amount));
|
||||
|
||||
}
|
||||
|
||||
//otherwise find the maximum healing for the unit
|
||||
else {
|
||||
int max_heal = 0;
|
||||
gamemap::location adjacent[6];
|
||||
get_adjacent_tiles(i->first,adjacent);
|
||||
for(int j = 0; j != 6; ++j) {
|
||||
std::map<gamemap::location,unit>::const_iterator healer =
|
||||
units.find(adjacent[j]);
|
||||
if(healer != units.end() && healer->second.side() == side) {
|
||||
max_heal = maximum(max_heal,
|
||||
healer->second.type().max_unit_healing());
|
||||
}
|
||||
}
|
||||
|
||||
if(max_heal > 0) {
|
||||
max_healing.insert(std::pair<gamemap::location,int>(i->first,
|
||||
max_heal));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//now see about units that can heal other units
|
||||
for(i = units.begin(); i != units.end(); ++i) {
|
||||
|
||||
if(i->second.side() == side && i->second.type().heals()) {
|
||||
gamemap::location adjacent[6];
|
||||
bool gets_healed[6];
|
||||
get_adjacent_tiles(i->first,adjacent);
|
||||
|
||||
int nhealed = 0;
|
||||
int j;
|
||||
for(j = 0; j != 6; ++j) {
|
||||
const std::map<gamemap::location,unit>::const_iterator adj =
|
||||
units.find(adjacent[j]);
|
||||
if(adj != units.end() &&
|
||||
adj->second.hitpoints() < adj->second.max_hitpoints() &&
|
||||
adj->second.side() == i->second.side() &&
|
||||
healed_units[adj->first] < max_healing[adj->first]) {
|
||||
++nhealed;
|
||||
gets_healed[j] = true;
|
||||
} else {
|
||||
gets_healed[j] = false;
|
||||
}
|
||||
}
|
||||
|
||||
if(nhealed == 0)
|
||||
continue;
|
||||
|
||||
const int healing_per_unit = i->second.type().heals()/nhealed;
|
||||
|
||||
for(j = 0; j != 6; ++j) {
|
||||
if(!gets_healed[j])
|
||||
continue;
|
||||
|
||||
assert(units.find(adjacent[j]) != units.end());
|
||||
|
||||
healed_units[adjacent[j]]
|
||||
= minimum(max_healing[adjacent[j]],
|
||||
healed_units[adjacent[j]]+healing_per_unit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//poisoned units will take the same amount of damage per turn, as
|
||||
//curing heals until they are reduced to 1 hitpoint. If they are
|
||||
//cured on a turn, they recover 0 hitpoints that turn, but they
|
||||
//are no longer poisoned
|
||||
for(i = units.begin(); i != units.end(); ++i) {
|
||||
|
||||
if(i->second.side() == side && i->second.has_flag("poisoned")) {
|
||||
const int damage = minimum<int>(game_config::cure_amount,
|
||||
i->second.hitpoints()-1);
|
||||
|
||||
if(damage > 0) {
|
||||
healed_units.insert(std::pair<gamemap::location,int>(
|
||||
i->first,-damage));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(std::map<gamemap::location,int>::iterator h = healed_units.begin();
|
||||
h != healed_units.end(); ++h) {
|
||||
|
||||
const gamemap::location& loc = h->first;
|
||||
|
||||
const bool show_healing = !disp.turbo() && !recorder.skipping();
|
||||
|
||||
assert(units.count(loc) == 1);
|
||||
|
||||
unit& u = units.find(loc)->second;
|
||||
|
||||
if(h->second > 0 && h->second > u.max_hitpoints()-u.hitpoints()) {
|
||||
h->second = u.max_hitpoints()-u.hitpoints();
|
||||
if(h->second <= 0)
|
||||
continue;
|
||||
}
|
||||
|
||||
if(show_healing) {
|
||||
disp.scroll_to_tile(loc.x,loc.y,display::WARP);
|
||||
disp.select_hex(loc);
|
||||
disp.update_display();
|
||||
}
|
||||
|
||||
const int DelayAmount = 50;
|
||||
|
||||
if(u.has_flag("poisoned") && h->second > 0) {
|
||||
|
||||
//poison is purged only if we are on a village or next to a curer
|
||||
if(h->second >= game_config::cure_amount ||
|
||||
max_healing[h->first] >= game_config::cure_amount) {
|
||||
u.remove_flag("poisoned");
|
||||
|
||||
if(show_healing) {
|
||||
sound::play_sound("heal.wav");
|
||||
SDL_Delay(DelayAmount);
|
||||
disp.invalidate_unit();
|
||||
disp.update_display();
|
||||
}
|
||||
}
|
||||
|
||||
h->second = 0;
|
||||
} else if(h->second < 0) {
|
||||
if(show_healing)
|
||||
sound::play_sound("groan.wav");
|
||||
} else if(h->second > 0) {
|
||||
if(show_healing)
|
||||
sound::play_sound("heal.wav");
|
||||
}
|
||||
|
||||
while(h->second > 0) {
|
||||
const display::Pixel heal_colour = disp.rgb(0,0,200);
|
||||
u.heal(1);
|
||||
|
||||
if(show_healing) {
|
||||
if((h->second%2) == 1)
|
||||
disp.draw_tile(loc.x,loc.y,NULL,0.5,heal_colour);
|
||||
else
|
||||
disp.draw_tile(loc.x,loc.y);
|
||||
SDL_Delay(DelayAmount);
|
||||
disp.update_display();
|
||||
}
|
||||
|
||||
--h->second;
|
||||
}
|
||||
|
||||
while(h->second < 0) {
|
||||
const display::Pixel damage_colour = disp.rgb(200,0,0);
|
||||
u.gets_hit(1);
|
||||
|
||||
if(show_healing) {
|
||||
if((h->second%2) == 1)
|
||||
disp.draw_tile(loc.x,loc.y,NULL,0.5,damage_colour);
|
||||
else
|
||||
disp.draw_tile(loc.x,loc.y);
|
||||
|
||||
SDL_Delay(DelayAmount);
|
||||
disp.update_display();
|
||||
}
|
||||
|
||||
++h->second;
|
||||
}
|
||||
|
||||
if(show_healing) {
|
||||
disp.draw_tile(loc.x,loc.y);
|
||||
disp.update_display();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unit get_advanced_unit(const game_data& info,
|
||||
std::map<gamemap::location,unit>& units,
|
||||
const gamemap::location& loc, const std::string& advance_to)
|
||||
{
|
||||
const std::map<std::string,unit_type>::const_iterator new_type =
|
||||
info.unit_types.find(advance_to);
|
||||
std::map<gamemap::location,unit>::iterator un = units.find(loc);
|
||||
if(new_type != info.unit_types.end() && un != units.end()) {
|
||||
const int side = un->second.side();
|
||||
|
||||
unit new_unit(&(new_type->second),un->second);
|
||||
|
||||
return new_unit;
|
||||
} else {
|
||||
throw gamestatus::game_error("Could not find the unit being advanced"
|
||||
" to: " + advance_to);
|
||||
}
|
||||
}
|
||||
|
||||
void advance_unit(const game_data& info,
|
||||
std::map<gamemap::location,unit>& units,
|
||||
const gamemap::location& loc, const std::string& advance_to)
|
||||
{
|
||||
const unit& new_unit = get_advanced_unit(info,units,loc,advance_to);
|
||||
units.erase(loc);
|
||||
units.insert(std::pair<gamemap::location,unit>(loc,new_unit));
|
||||
}
|
||||
|
||||
int check_victory(std::map<gamemap::location,unit>& units)
|
||||
{
|
||||
std::set<int> seen_leaders;
|
||||
for(std::map<gamemap::location,unit>::const_iterator i = units.begin();
|
||||
i != units.end(); ++i) {
|
||||
if(i->second.can_recruit())
|
||||
seen_leaders.insert(i->second.side());
|
||||
}
|
||||
|
||||
//if only one leader remains standing, his team has won
|
||||
if(seen_leaders.size() == 1)
|
||||
return *seen_leaders.begin();
|
||||
|
||||
//if the player (team 1) isn't here, the player has lost
|
||||
if(seen_leaders.count(1) == 0)
|
||||
return 2;
|
||||
|
||||
//remove any units which are leaderless
|
||||
for(std::map<gamemap::location,unit>::iterator j = units.begin();
|
||||
j != units.end(); ++j) {
|
||||
if(seen_leaders.count(j->second.side()) == 0) {
|
||||
units.erase(j);
|
||||
j = units.begin();
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
gamestatus::TIME timeofday_at(const gamestatus& status,
|
||||
const std::map<gamemap::location,unit>& units,
|
||||
const gamemap::location& loc)
|
||||
{
|
||||
gamemap::location locs[7];
|
||||
locs[0] = loc;
|
||||
get_adjacent_tiles(loc,locs+1);
|
||||
|
||||
bool lighten = false;
|
||||
for(int i = 0; i != 7; ++i) {
|
||||
const std::map<gamemap::location,unit>::const_iterator itor =
|
||||
units.find(locs[i]);
|
||||
if(itor != units.end() &&
|
||||
itor->second.type().is_lightbringer()) {
|
||||
lighten = true;
|
||||
}
|
||||
}
|
||||
|
||||
gamestatus::TIME res = status.timeofday();
|
||||
if(lighten) {
|
||||
if(res == gamestatus::DUSK)
|
||||
res = gamestatus::DAY2;
|
||||
else if(res > gamestatus::DUSK)
|
||||
res = gamestatus::DUSK;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
double combat_modifier(const gamestatus& status,
|
||||
const std::map<gamemap::location,unit>& units,
|
||||
const gamemap::location& loc,
|
||||
unit_type::ALIGNMENT alignment)
|
||||
{
|
||||
const gamestatus::TIME timeofday = timeofday_at(status,units,loc);
|
||||
if(alignment == unit_type::LAWFUL) {
|
||||
if(timeofday > gamestatus::DAWN && timeofday < gamestatus::DUSK)
|
||||
return 1.25;
|
||||
else if(timeofday > gamestatus::DUSK &&
|
||||
units.find(loc)->second.type().nightvision() == false)
|
||||
return 0.75;
|
||||
} else if(alignment == unit_type::CHAOTIC) {
|
||||
if(timeofday > gamestatus::DAWN && timeofday < gamestatus::DUSK)
|
||||
return 0.75;
|
||||
else if(timeofday > gamestatus::DUSK)
|
||||
return 1.25;
|
||||
}
|
||||
|
||||
return 1.0;
|
||||
}
|
98
src/actions.hpp
Normal file
98
src/actions.hpp
Normal file
|
@ -0,0 +1,98 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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
|
||||
|
||||
#include "display.hpp"
|
||||
#include "gamestatus.hpp"
|
||||
#include "map.hpp"
|
||||
#include "unit.hpp"
|
||||
#include "unit_types.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
std::string recruit_unit(const gamemap& map, int team,
|
||||
std::map<gamemap::location,unit>& units,
|
||||
unit& unit, gamemap::location preferred_location,
|
||||
display *disp=NULL);
|
||||
|
||||
struct battle_stats
|
||||
{
|
||||
std::string attack_name, defend_name;
|
||||
std::string attack_type, defend_type;
|
||||
std::string attack_special, defend_special;
|
||||
std::string range;
|
||||
double chance_to_hit_attacker, chance_to_hit_defender;
|
||||
int damage_attacker_takes, damage_defender_takes;
|
||||
int amount_attacker_drains, amount_defender_drains;
|
||||
int ndefends, nattacks;
|
||||
int attack_with, defend_with;
|
||||
bool attacker_plague, defender_plague;
|
||||
};
|
||||
|
||||
battle_stats evaluate_battle_stats(
|
||||
const gamemap& map,
|
||||
const gamemap::location& attacker,
|
||||
const gamemap::location& defender,
|
||||
int attack_with,
|
||||
std::map<gamemap::location,unit>& units,
|
||||
const gamestatus& state,
|
||||
const game_data& info,
|
||||
gamemap::TERRAIN attacker_terrain_override=0,
|
||||
bool include_strings=true);
|
||||
|
||||
void attack(display& gui, const gamemap& map,
|
||||
const gamemap::location& attacker,
|
||||
const gamemap::location& defender,
|
||||
int attack_with,
|
||||
std::map<gamemap::location,unit>& units,
|
||||
const gamestatus& state,
|
||||
const game_data& info, bool player_is_attacker);
|
||||
|
||||
int tower_owner(const gamemap::location& loc, std::vector<team>& teams);
|
||||
|
||||
void get_tower(const gamemap::location& loc, std::vector<team>& teams,
|
||||
int team_num);
|
||||
|
||||
std::map<gamemap::location,unit>::iterator
|
||||
find_leader(std::map<gamemap::location,unit>& units, int side);
|
||||
|
||||
void calculate_healing(display& disp, const gamemap& map,
|
||||
std::map<gamemap::location,unit>& units, int side);
|
||||
|
||||
unit get_advanced_unit(const game_data& info,
|
||||
std::map<gamemap::location,unit>& units,
|
||||
const gamemap::location& loc, const std::string& advance_to);
|
||||
|
||||
void advance_unit(const game_data& info,
|
||||
std::map<gamemap::location,unit>& units,
|
||||
const gamemap::location& loc, const std::string& advance_to);
|
||||
|
||||
bool under_leadership(const std::map<gamemap::location,unit>& units,
|
||||
const gamemap::location& loc);
|
||||
|
||||
int check_victory(std::map<gamemap::location,unit>& units);
|
||||
|
||||
//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 brings light is there
|
||||
gamestatus::TIME timeofday_at(const gamestatus& status,
|
||||
const std::map<gamemap::location,unit>& units,
|
||||
const gamemap::location& loc);
|
||||
|
||||
double combat_modifier(const gamestatus& status,
|
||||
const std::map<gamemap::location,unit>& units,
|
||||
const gamemap::location& loc,
|
||||
unit_type::ALIGNMENT alignment);
|
||||
|
||||
#endif
|
504
src/ai.cpp
Normal file
504
src/ai.cpp
Normal file
|
@ -0,0 +1,504 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "actions.hpp"
|
||||
#include "ai.hpp"
|
||||
#include "ai_attack.hpp"
|
||||
#include "ai_move.hpp"
|
||||
#include "dialogs.hpp"
|
||||
#include "game_config.hpp"
|
||||
#include "log.hpp"
|
||||
#include "menu.hpp"
|
||||
#include "pathfind.hpp"
|
||||
#include "playlevel.hpp"
|
||||
#include "playturn.hpp"
|
||||
#include "replay.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace {
|
||||
|
||||
bool recruit(const gamemap& map, const gamemap::location& leader,
|
||||
const std::string& usage, const game_data& gameinfo,
|
||||
int team_num, team& tm, int min_gold,
|
||||
std::map<gamemap::location,unit>& units, display& disp)
|
||||
{
|
||||
log_scope("recruiting troops");
|
||||
std::cerr << "recruiting " << usage << "\n";
|
||||
|
||||
std::vector<std::map<std::string,unit_type>::const_iterator> options;
|
||||
|
||||
//record the number of the recruit for replay recording
|
||||
std::vector<int> option_numbers;
|
||||
|
||||
//find an available unit that can be recruited, matches the desired
|
||||
//usage type, and comes in under budget
|
||||
const std::set<std::string>& recruits = tm.recruits();
|
||||
for(std::map<std::string,unit_type>::const_iterator i =
|
||||
gameinfo.unit_types.begin(); i != gameinfo.unit_types.end(); ++i) {
|
||||
|
||||
if(i->second.usage() == usage && recruits.count(i->second.name())
|
||||
&& tm.gold() - i->second.cost() > min_gold) {
|
||||
|
||||
options.push_back(i);
|
||||
option_numbers.push_back(std::distance(recruits.begin(),
|
||||
recruits.find(i->first)));
|
||||
}
|
||||
}
|
||||
|
||||
//from the available options, choose one at random
|
||||
if(options.empty() == false) {
|
||||
const int option = rand()%options.size();
|
||||
recorder.add_recruit(option_numbers[option],gamemap::location());
|
||||
const unit_type& u = options[option]->second;
|
||||
tm.spend_gold(u.cost());
|
||||
unit new_unit(&u,team_num,true);
|
||||
|
||||
std::cerr << "recruiting a " << u.name() << " for " << u.cost() << " have " << tm.gold() << " left\n";
|
||||
const gamemap::location loc;
|
||||
return recruit_unit(map,team_num,units,new_unit,loc,&disp).empty();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace ai {
|
||||
|
||||
void move_unit(const game_data& gameinfo, display& disp,
|
||||
const gamemap& map,
|
||||
std::map<gamemap::location,unit>& units,
|
||||
const location& from, const location& to,
|
||||
std::map<location,paths>& possible_moves,
|
||||
std::vector<team>& teams, int team_num)
|
||||
{
|
||||
assert(units.find(to) == units.end() || from == to);
|
||||
|
||||
disp.select_hex(from);
|
||||
disp.update_display();
|
||||
|
||||
log_scope("move_unit");
|
||||
std::map<location,unit>::iterator u_it = units.find(from);
|
||||
if(u_it == units.end()) {
|
||||
std::cout << "Could not find unit at " << from.x << ", "
|
||||
<< from.y << "\n";
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
recorder.add_movement(from,to);
|
||||
|
||||
const bool ignore_zocs = u_it->second.type().is_skirmisher();
|
||||
const bool teleport = u_it->second.type().teleports();
|
||||
paths current_paths = paths(map,gameinfo,units,from,teams,
|
||||
ignore_zocs,teleport);
|
||||
paths_wiper wiper(disp);
|
||||
disp.set_paths(¤t_paths);
|
||||
|
||||
disp.scroll_to_tiles(from.x,from.y,to.x,to.y);
|
||||
|
||||
unit current_unit = u_it->second;
|
||||
units.erase(u_it);
|
||||
|
||||
const std::map<location,paths>::iterator p_it = possible_moves.find(from);
|
||||
|
||||
if(p_it != possible_moves.end()) {
|
||||
paths& p = p_it->second;
|
||||
std::map<location,paths::route>::iterator rt = p.routes.begin();
|
||||
for(; rt != p.routes.end(); ++rt) {
|
||||
if(rt->first == to) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(rt != p.routes.end()) {
|
||||
std::vector<location> steps = rt->second.steps;
|
||||
steps.push_back(to); //add the destination to the steps
|
||||
disp.move_unit(steps,current_unit);
|
||||
}
|
||||
}
|
||||
|
||||
current_unit.set_movement(0);
|
||||
units.insert(std::pair<location,unit>(to,current_unit));
|
||||
if(map[to.x][to.y] == gamemap::TOWER)
|
||||
get_tower(to,teams,team_num-1);
|
||||
|
||||
disp.draw_tile(to.x,to.y);
|
||||
disp.draw();
|
||||
|
||||
game_events::fire("moveto",to);
|
||||
}
|
||||
|
||||
void do_move(display& disp, const gamemap& map, const game_data& gameinfo,
|
||||
std::map<gamemap::location,unit>& units,
|
||||
std::vector<team>& teams, int team_num, const gamestatus& state,
|
||||
bool consider_combat, std::vector<target>* additional_targets)
|
||||
{
|
||||
std::vector<target> tgts;
|
||||
if(additional_targets == NULL)
|
||||
additional_targets = &tgts;
|
||||
|
||||
log_scope("doing ai move");
|
||||
|
||||
team& current_team = teams[team_num-1];
|
||||
|
||||
typedef paths::route route;
|
||||
|
||||
std::multimap<location,location> srcdst;
|
||||
std::multimap<location,location> dstsrc;
|
||||
|
||||
std::vector<gamemap::location> leader_locations;
|
||||
|
||||
typedef std::map<location,paths> moves_map;
|
||||
moves_map possible_moves;
|
||||
for(std::map<gamemap::location,unit>::const_iterator un_it = units.begin();
|
||||
un_it != units.end(); ++un_it) {
|
||||
|
||||
if(un_it->second.side() != team_num) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//insert the trivial moves of staying on the same location
|
||||
if(un_it->second.movement_left() == un_it->second.total_movement()) {
|
||||
std::pair<location,location> trivial_mv(un_it->first,un_it->first);
|
||||
srcdst.insert(trivial_mv);
|
||||
dstsrc.insert(trivial_mv);
|
||||
}
|
||||
|
||||
if(un_it->second.can_recruit()) {
|
||||
//save so we can remove from possible moves later
|
||||
leader_locations.push_back(un_it->first);
|
||||
continue;
|
||||
}
|
||||
|
||||
const bool ignore_zocs = un_it->second.type().is_skirmisher();
|
||||
const bool teleports = un_it->second.type().teleports();
|
||||
possible_moves.insert(std::pair<gamemap::location,paths>(
|
||||
un_it->first,paths(map,gameinfo,units,
|
||||
un_it->first,teams,ignore_zocs,teleports)));
|
||||
}
|
||||
|
||||
|
||||
for(moves_map::iterator m = possible_moves.begin();
|
||||
m != possible_moves.end(); ++m) {
|
||||
for(std::map<location,route>::iterator rtit =
|
||||
m->second.routes.begin(); rtit != m->second.routes.end();
|
||||
++rtit) {
|
||||
const location& src = m->first;
|
||||
const location& dst = rtit->first;
|
||||
|
||||
if(src != dst && units.find(dst) == units.end()) {
|
||||
srcdst.insert(std::pair<location,location>(src,dst));
|
||||
dstsrc.insert(std::pair<location,location>(dst,src));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//no moves left, recruitment phase
|
||||
//take stock of our current set of units
|
||||
if(srcdst.empty()) {
|
||||
std::cout << "recruitment......\n";
|
||||
location leader;
|
||||
int num_units = 0;
|
||||
std::map<std::string,int> unit_types;
|
||||
for(std::map<location,unit>::const_iterator i = units.begin();
|
||||
i != units.end(); ++i) {
|
||||
if(i->second.side() != team_num)
|
||||
continue;
|
||||
|
||||
if(i->second.can_recruit()) {
|
||||
leader = i->first;
|
||||
continue;
|
||||
}
|
||||
|
||||
unit_types[i->second.type().usage()]++;
|
||||
++num_units;
|
||||
}
|
||||
|
||||
const int cash_flow = current_team.towers()*game_config::tower_income +
|
||||
game_config::base_income - num_units;
|
||||
|
||||
const int min_gold = 10 + (cash_flow < 0 ? -cash_flow*10 : 0);
|
||||
|
||||
//count the number of scouts we have currently
|
||||
|
||||
const int towers = map.towers().size();
|
||||
int taken_towers = 0;
|
||||
for(int j = 0; j != teams.size(); ++j) {
|
||||
taken_towers += teams[j].towers();
|
||||
}
|
||||
|
||||
const int neutral_towers = towers - taken_towers;
|
||||
|
||||
//we want at least one scout for every eight neutral towers
|
||||
int scouts_wanted = neutral_towers/8;
|
||||
if(scouts_wanted < 1)
|
||||
scouts_wanted = 1;
|
||||
while(unit_types["scout"] < scouts_wanted) {
|
||||
if(recruit(map,leader,"scout",gameinfo,team_num,current_team,
|
||||
min_gold,units,disp) == false)
|
||||
break;
|
||||
|
||||
++unit_types["scout"];
|
||||
}
|
||||
|
||||
const std::vector<std::string>& options =
|
||||
current_team.recruitment_pattern();
|
||||
|
||||
if(options.empty()) {
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
//buy fighters as long as we have room and can afford it
|
||||
while(recruit(map,leader,options[rand()%options.size()].c_str(),
|
||||
gameinfo,team_num,current_team,min_gold,units,disp)) {
|
||||
|
||||
}
|
||||
|
||||
recorder.end_turn();
|
||||
return;
|
||||
}
|
||||
|
||||
int ticks = SDL_GetTicks();
|
||||
//look for targets of opportunity that we are hoping to kill this turn
|
||||
std::vector<attack_analysis> analysis;
|
||||
|
||||
if(consider_combat)
|
||||
analysis = analyze_targets(map,srcdst,dstsrc,units,
|
||||
current_team,team_num,state,gameinfo);
|
||||
|
||||
int time_taken = SDL_GetTicks() - ticks;
|
||||
std::cout << "took " << time_taken << " ticks for " << analysis.size() << " positions. Analyzing...\n";
|
||||
|
||||
ticks = SDL_GetTicks();
|
||||
|
||||
const int max_sims = 30000;
|
||||
int num_sims = analysis.empty() ? 0 : max_sims/analysis.size();
|
||||
if(num_sims < 8)
|
||||
num_sims = 8;
|
||||
if(num_sims > 20)
|
||||
num_sims = 20;
|
||||
|
||||
std::cout << "simulations: " << num_sims << "\n";
|
||||
|
||||
const int max_positions = 20000;
|
||||
const int skip_num = analysis.size()/max_positions;
|
||||
|
||||
std::vector<attack_analysis>::iterator choice_it = analysis.end();
|
||||
double choice_rating = -1000.0;
|
||||
for(std::vector<attack_analysis>::iterator it = analysis.begin();
|
||||
it != analysis.end(); ++it) {
|
||||
if(skip_num > 0 && ((it - analysis.begin())%skip_num) &&
|
||||
it->movements.size() > 1)
|
||||
continue;
|
||||
|
||||
const double rating = it->rating(current_team.aggression());
|
||||
std::cout << "attack option rated at " << rating << " (" << current_team.aggression() << ")\n";
|
||||
if(rating > choice_rating) {
|
||||
choice_it = it;
|
||||
choice_rating = rating;
|
||||
}
|
||||
}
|
||||
|
||||
time_taken = SDL_GetTicks() - ticks;
|
||||
std::cout << "analysis took " << time_taken << " ticks\n";
|
||||
|
||||
if(choice_rating > 0.0) {
|
||||
const location& from = choice_it->movements[0].first;
|
||||
const location& to = choice_it->movements[0].second;
|
||||
const location& target_loc = choice_it->target;
|
||||
const int weapon = choice_it->weapons[0];
|
||||
|
||||
const std::map<gamemap::location,unit>::const_iterator tgt =
|
||||
units.find(target_loc);
|
||||
|
||||
const bool defender_human = (tgt != units.end()) ?
|
||||
teams[tgt->second.side()-1].is_human() : false;
|
||||
|
||||
move_unit(gameinfo,disp,map,units,from,to,
|
||||
possible_moves,teams,team_num);
|
||||
|
||||
std::cerr << "attacking...\n";
|
||||
recorder.add_attack(to,target_loc,weapon);
|
||||
|
||||
game_events::fire("attack",to,target_loc);
|
||||
if(units.count(to) && units.count(target_loc)) {
|
||||
attack(disp,map,to,target_loc,weapon,units,state,gameinfo,false);
|
||||
const int res = check_victory(units);
|
||||
if(res == 1)
|
||||
throw end_level_exception(VICTORY);
|
||||
else if(res > 1)
|
||||
throw end_level_exception(DEFEAT);
|
||||
}
|
||||
std::cerr << "done attacking...\n";
|
||||
|
||||
//if this is the only unit in the planned attack, and the target
|
||||
//is still alive, then also summon reinforcements
|
||||
if(choice_it->movements.size() == 1 && units.count(target_loc)) {
|
||||
additional_targets->push_back(target(target_loc,3.0));
|
||||
}
|
||||
|
||||
dialogs::advance_unit(gameinfo,units,to,disp,true);
|
||||
dialogs::advance_unit(gameinfo,units,target_loc,disp,!defender_human);
|
||||
|
||||
do_move(disp,map,gameinfo,units,teams,team_num,state,consider_combat,
|
||||
additional_targets);
|
||||
return;
|
||||
|
||||
} else {
|
||||
log_scope("summoning reinforcements...\n");
|
||||
consider_combat = false;
|
||||
|
||||
std::set<gamemap::location> already_done;
|
||||
|
||||
for(std::vector<attack_analysis>::iterator it = analysis.begin();
|
||||
it != analysis.end(); ++it) {
|
||||
assert(it->movements.empty() == false);
|
||||
const gamemap::location& loc = it->movements.front().first;
|
||||
if(already_done.count(loc) > 0)
|
||||
continue;
|
||||
|
||||
additional_targets->push_back(target(loc,3.0));
|
||||
already_done.insert(loc);
|
||||
}
|
||||
}
|
||||
|
||||
//mark the leader as having moved and attacked by now
|
||||
for(std::vector<location>::const_iterator lead = leader_locations.begin();
|
||||
lead != leader_locations.end(); ++lead) {
|
||||
const std::map<location,unit>::iterator leader = units.find(*lead);
|
||||
if(leader != units.end()) {
|
||||
leader->second.set_movement(0);
|
||||
leader->second.set_attacked();
|
||||
}
|
||||
|
||||
//remove leader from further consideration
|
||||
srcdst.erase(*lead);
|
||||
dstsrc.erase(*lead);
|
||||
}
|
||||
|
||||
//try to acquire towers
|
||||
for(std::multimap<location,location>::const_iterator i = dstsrc.begin();
|
||||
i != dstsrc.end(); ++i) {
|
||||
if(map[i->first.x][i->first.y] != gamemap::TOWER)
|
||||
continue;
|
||||
|
||||
bool want_tower = true;
|
||||
for(int j = 0; j != teams.size(); ++j) {
|
||||
if(!current_team.is_enemy(j+1) && teams[j].owns_tower(i->first)) {
|
||||
want_tower = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(want_tower) {
|
||||
const std::map<location,unit>::iterator un = units.find(i->second);
|
||||
if(un == units.end()) {
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if(un->second.is_guardian())
|
||||
continue;
|
||||
|
||||
move_unit(gameinfo,disp,map,units,i->second,i->first,
|
||||
possible_moves,teams,team_num);
|
||||
|
||||
do_move(disp,map,gameinfo,units,teams,team_num,
|
||||
state,consider_combat,additional_targets);
|
||||
return;
|
||||
}
|
||||
}
|
||||
std::cout << "b\n";
|
||||
|
||||
//find units in need of healing
|
||||
std::map<location,unit>::iterator u_it = units.begin();
|
||||
for(; u_it != units.end(); ++u_it) {
|
||||
unit& u = u_it->second;
|
||||
|
||||
//if the unit is on our side, has lost as many or more than 1/2 round
|
||||
//worth of healing, and doesn't regenerate itself, then try to
|
||||
//find a vacant village for it to rest in
|
||||
if(u.side() == team_num &&
|
||||
u.type().hitpoints() - u.hitpoints() >= game_config::heal_amount/2 &&
|
||||
!u.type().regenerates()) {
|
||||
typedef std::multimap<location,location>::iterator Itor;
|
||||
std::pair<Itor,Itor> it = srcdst.equal_range(u_it->first);
|
||||
while(it.first != it.second) {
|
||||
const location& dst = it.first->second;
|
||||
if(map[dst.x][dst.y] == gamemap::TOWER &&
|
||||
units.find(dst) == units.end()) {
|
||||
const location& src = it.first->first;
|
||||
|
||||
move_unit(gameinfo,disp,map,units,src,dst,
|
||||
possible_moves,teams,team_num);
|
||||
do_move(disp,map,gameinfo,units,teams,team_num,state,
|
||||
consider_combat,additional_targets);
|
||||
return;
|
||||
}
|
||||
|
||||
++it.first;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(dstsrc.empty()) {
|
||||
do_move(disp,map,gameinfo,units,teams,team_num,state,
|
||||
consider_combat,additional_targets);
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "finding targets...\n";
|
||||
std::vector<target> targets = find_targets(map,units,teams,team_num);
|
||||
targets.insert(targets.end(),additional_targets->begin(),
|
||||
additional_targets->end());
|
||||
for(;;) {
|
||||
if(targets.empty()) {
|
||||
recorder.end_turn();
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "choosing move...\n";
|
||||
std::pair<location,location> move = choose_move(targets,dstsrc,units,
|
||||
map,teams,team_num,
|
||||
gameinfo);
|
||||
for(std::vector<target>::const_iterator ittg = targets.begin();
|
||||
ittg != targets.end(); ++ittg) {
|
||||
assert(map.on_board(ittg->loc));
|
||||
}
|
||||
|
||||
|
||||
if(move.first.valid() == false)
|
||||
break;
|
||||
|
||||
std::cout << "move: " << move.first.x << ", " << move.first.y << " - " << move.second.x << ", " << move.second.y << "\n";
|
||||
|
||||
std::cout << "calling move_unit\n";
|
||||
move_unit(gameinfo,disp,map,units,move.first,move.second,
|
||||
possible_moves,teams,team_num);
|
||||
std::cout << "end move_unit\n";
|
||||
|
||||
//don't allow any other units to move onto the tile our unit
|
||||
//just moved onto
|
||||
typedef std::multimap<location,location>::iterator Itor;
|
||||
std::pair<Itor,Itor> del = dstsrc.equal_range(move.second);
|
||||
dstsrc.erase(del.first,del.second);
|
||||
}
|
||||
|
||||
do_move(disp,map,gameinfo,units,teams,team_num,state,
|
||||
consider_combat,additional_targets);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
34
src/ai.hpp
Normal file
34
src/ai.hpp
Normal file
|
@ -0,0 +1,34 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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 AI_HPP_INCLUDED
|
||||
#define AI_HPP_INCLUDED
|
||||
|
||||
#include "ai_move.hpp"
|
||||
#include "display.hpp"
|
||||
#include "map.hpp"
|
||||
#include "unit.hpp"
|
||||
#include "unit_types.hpp"
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace ai {
|
||||
typedef gamemap::location location;
|
||||
|
||||
void do_move(display& disp, const gamemap& map, const game_data& gameinfo,
|
||||
std::map<gamemap::location,unit>& units,
|
||||
std::vector<team>& teams, int team_num, const gamestatus& state,
|
||||
bool consider_combat=true,
|
||||
std::vector<target>* additional_targets=NULL);
|
||||
}
|
||||
|
||||
#endif
|
441
src/ai_attack.cpp
Normal file
441
src/ai_attack.cpp
Normal file
|
@ -0,0 +1,441 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "actions.hpp"
|
||||
#include "ai_attack.hpp"
|
||||
#include "game_config.hpp"
|
||||
#include "log.hpp"
|
||||
#include "pathfind.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <set>
|
||||
|
||||
namespace {
|
||||
|
||||
const int max_positions = 10000;
|
||||
|
||||
using namespace ai;
|
||||
|
||||
void do_analysis(
|
||||
const gamemap& map,
|
||||
const location& loc,
|
||||
const std::multimap<location,location>& srcdst,
|
||||
const std::multimap<location,location>& dstsrc,
|
||||
const location* tiles, bool* used_locations,
|
||||
std::vector<location>& units,
|
||||
std::map<gamemap::location,unit>& units_map,
|
||||
std::vector<attack_analysis>& result,
|
||||
const game_data& data, const gamestatus& status,
|
||||
attack_analysis& cur_analysis
|
||||
)
|
||||
{
|
||||
if(cur_analysis.movements.size() >= 6)
|
||||
return;
|
||||
|
||||
static double best_results[6];
|
||||
if(result.empty()) {
|
||||
for(int i = 0; i != 6; ++i) {
|
||||
best_results[i] = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
// if(result.size() > max_positions && !cur_analysis.movements.empty())
|
||||
// return;
|
||||
|
||||
const double cur_rating = cur_analysis.movements.empty() ? 0 :
|
||||
cur_analysis.rating(0.0);
|
||||
|
||||
double rating_to_beat = cur_rating;
|
||||
|
||||
if(!cur_analysis.movements.empty()) {
|
||||
assert(cur_analysis.movements.size() < 6);
|
||||
double& best_res = best_results[cur_analysis.movements.size()-1];
|
||||
rating_to_beat = best_res = maximum(best_res,cur_rating);
|
||||
}
|
||||
|
||||
for(int i = 0; i != units.size(); ++i) {
|
||||
const location current_unit = units[i];
|
||||
units.erase(units.begin() + i);
|
||||
|
||||
for(int j = 0; j != 6; ++j) {
|
||||
if(used_locations[j])
|
||||
continue;
|
||||
|
||||
typedef std::multimap<location,location>::const_iterator Itor;
|
||||
std::pair<Itor,Itor> its = dstsrc.equal_range(tiles[j]);
|
||||
while(its.first != its.second) {
|
||||
if(its.first->second == current_unit)
|
||||
break;
|
||||
++its.first;
|
||||
}
|
||||
|
||||
if(its.first == its.second)
|
||||
continue;
|
||||
|
||||
cur_analysis.movements.push_back(
|
||||
std::pair<location,location>(current_unit,tiles[j]));
|
||||
|
||||
cur_analysis.analyze(map,units_map,status,data,50);
|
||||
|
||||
if(cur_analysis.rating(0.0) > rating_to_beat) {
|
||||
|
||||
result.push_back(cur_analysis);
|
||||
used_locations[j] = true;
|
||||
do_analysis(map,loc,srcdst,dstsrc,tiles,used_locations,
|
||||
units,units_map,result,data,status,cur_analysis);
|
||||
used_locations[j] = false;
|
||||
}
|
||||
|
||||
cur_analysis.movements.pop_back();
|
||||
}
|
||||
|
||||
units.insert(units.begin() + i, current_unit);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace ai {
|
||||
|
||||
struct battle_type {
|
||||
battle_type(const gamemap::location& a, const gamemap::location& d,
|
||||
gamemap::TERRAIN t)
|
||||
: attacker(a),defender(d),terrain(t),weapon(-1)
|
||||
{}
|
||||
|
||||
const gamemap::location attacker;
|
||||
const gamemap::location defender;
|
||||
const gamemap::TERRAIN terrain;
|
||||
int weapon;
|
||||
battle_stats stats;
|
||||
};
|
||||
|
||||
bool operator<(const battle_type& a, const battle_type& b)
|
||||
{
|
||||
return a.attacker < b.attacker ||
|
||||
a.attacker == b.attacker && a.defender < b.defender ||
|
||||
a.attacker == b.attacker && a.defender == b.defender &&
|
||||
a.terrain < b.terrain;
|
||||
}
|
||||
|
||||
bool operator==(const battle_type& a, const battle_type& b)
|
||||
{
|
||||
return a.attacker == b.attacker && a.defender == b.defender &&
|
||||
a.terrain == b.terrain;
|
||||
}
|
||||
|
||||
std::set<battle_type> weapon_choice_cache;
|
||||
|
||||
int choose_weapon(const gamemap& map, std::map<location,unit>& units,
|
||||
const gamestatus& status, const game_data& info,
|
||||
const location& att, const location& def,
|
||||
battle_stats& cur_stats, gamemap::TERRAIN terrain)
|
||||
{
|
||||
const std::map<location,unit>::const_iterator itor = units.find(att);
|
||||
if(itor == units.end())
|
||||
return -1;
|
||||
|
||||
static int cache_hits = 0;
|
||||
static int cache_misses = 0;
|
||||
|
||||
battle_type battle(att,def,terrain);
|
||||
const std::set<battle_type>::const_iterator cache_itor
|
||||
= weapon_choice_cache.find(battle);
|
||||
|
||||
if(cache_itor != weapon_choice_cache.end()) {
|
||||
assert(*cache_itor == battle);
|
||||
|
||||
++cache_hits;
|
||||
cur_stats = cache_itor->stats;
|
||||
|
||||
if(!(cache_itor->weapon >= 0 &&
|
||||
cache_itor->weapon < itor->second.attacks().size())) {
|
||||
}
|
||||
|
||||
assert(cache_itor->weapon >= 0 &&
|
||||
cache_itor->weapon < itor->second.attacks().size());
|
||||
return cache_itor->weapon;
|
||||
}
|
||||
|
||||
++cache_misses;
|
||||
|
||||
if((cache_misses%100) == 0) {
|
||||
std::cerr << "cache_stats: " << cache_hits << ":" << cache_misses << " " << weapon_choice_cache.size() << "\n";
|
||||
}
|
||||
|
||||
int current_choice = -1;
|
||||
double current_rating = 0.0;
|
||||
const std::vector<attack_type>& attacks = itor->second.attacks();
|
||||
assert(!attacks.empty());
|
||||
|
||||
for(int a = 0; a != attacks.size(); ++a) {
|
||||
const battle_stats stats = evaluate_battle_stats(map,att,def,a,units,
|
||||
status,info,terrain,false);
|
||||
|
||||
//TODO: improve this rating formula!
|
||||
const double rating =
|
||||
stats.chance_to_hit_defender*stats.damage_defender_takes*
|
||||
stats.nattacks -
|
||||
stats.chance_to_hit_attacker*stats.damage_attacker_takes*
|
||||
stats.ndefends;
|
||||
if(rating > current_rating || current_choice == -1) {
|
||||
current_choice = a;
|
||||
current_rating = rating;
|
||||
cur_stats = stats;
|
||||
}
|
||||
}
|
||||
|
||||
assert(current_choice >= 0 && current_choice < attacks.size());
|
||||
|
||||
battle.stats = cur_stats;
|
||||
battle.weapon = current_choice;
|
||||
weapon_choice_cache.insert(battle);
|
||||
|
||||
return current_choice;
|
||||
}
|
||||
|
||||
void attack_analysis::analyze(const gamemap& map,
|
||||
std::map<location,unit>& units,
|
||||
const gamestatus& status,
|
||||
const game_data& info, int num_sims)
|
||||
{
|
||||
const std::map<location,unit>::const_iterator defend_it =units.find(target);
|
||||
assert(defend_it != units.end());
|
||||
|
||||
target_value = defend_it->second.type().cost();
|
||||
target_value += (double(defend_it->second.experience())/
|
||||
double(defend_it->second.max_experience()))*target_value;
|
||||
target_starting_damage = defend_it->second.max_hitpoints() -
|
||||
defend_it->second.hitpoints();
|
||||
chance_to_kill = 0.0;
|
||||
avg_damage_inflicted = 0.0;
|
||||
avg_damage_taken = 0.0;
|
||||
resources_used = 0.0;
|
||||
terrain_quality = 0.0;
|
||||
counter_strength_ratio = 0.0;
|
||||
avg_losses = 0.0;
|
||||
|
||||
const int target_max_hp = defend_it->second.max_hitpoints();
|
||||
const int target_hp = defend_it->second.hitpoints();
|
||||
static std::vector<int> hitpoints;
|
||||
static std::vector<battle_stats> stats;
|
||||
|
||||
hitpoints.clear();
|
||||
stats.clear();
|
||||
weapons.clear();
|
||||
|
||||
std::vector<std::pair<location,location> >::const_iterator m;
|
||||
for(m = movements.begin(); m != movements.end(); ++m) {
|
||||
battle_stats bat_stats;
|
||||
const int weapon = choose_weapon(map,units,status,info,
|
||||
m->first,target, bat_stats,
|
||||
map[m->second.x][m->second.y]);
|
||||
|
||||
assert(weapon != -1);
|
||||
weapons.push_back(weapon);
|
||||
|
||||
stats.push_back(bat_stats);
|
||||
hitpoints.push_back(units.find(m->first)->second.hitpoints());
|
||||
}
|
||||
|
||||
for(int j = 0; j != num_sims; ++j) {
|
||||
|
||||
int defenderxp = 0;
|
||||
|
||||
int defhp = target_hp;
|
||||
for(int i = 0; i != movements.size() && defhp; ++i) {
|
||||
const battle_stats& stat = stats[i];
|
||||
int atthp = hitpoints[i];
|
||||
|
||||
int attacks = stat.nattacks;
|
||||
int defends = stat.ndefends;
|
||||
|
||||
std::map<location,unit>::const_iterator att
|
||||
= units.find(movements[i].first);
|
||||
double cost = att->second.type().cost();
|
||||
|
||||
//up to double the value of a unit based on experience
|
||||
cost += (double(att->second.experience())/
|
||||
double(att->second.max_experience()))*cost;
|
||||
|
||||
terrain_quality += stat.chance_to_hit_attacker*cost;
|
||||
resources_used += cost;
|
||||
|
||||
while(attacks || defends) {
|
||||
if(attacks) {
|
||||
const double roll = double(rand()%1000)/1000.0;
|
||||
if(roll < stat.chance_to_hit_defender) {
|
||||
defhp -= stat.damage_defender_takes;
|
||||
if(defhp <= 0) {
|
||||
|
||||
//the reward for advancing a unit is to
|
||||
//get a 'negative' loss of that unit
|
||||
const int xp = defend_it->second.type().level()*10;
|
||||
if(xp >= att->second.max_experience() -
|
||||
att->second.experience()) {
|
||||
avg_losses -= att->second.type().cost();
|
||||
}
|
||||
|
||||
//the reward for killing with a unit that
|
||||
//plagues is to get a 'negative' loss of that unit
|
||||
if(stat.attacker_plague) {
|
||||
avg_losses -= att->second.type().cost();
|
||||
}
|
||||
|
||||
defhp = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
atthp += stat.amount_attacker_drains;
|
||||
if(atthp > hitpoints[i])
|
||||
atthp = hitpoints[i];
|
||||
}
|
||||
|
||||
--attacks;
|
||||
}
|
||||
|
||||
if(defends) {
|
||||
const double roll = double(rand()%1000)/1000.0;
|
||||
if(roll < stat.chance_to_hit_attacker) {
|
||||
atthp -= stat.damage_attacker_takes;
|
||||
if(atthp <= 0) {
|
||||
atthp = 0;
|
||||
|
||||
//penalty for allowing plague is a 'negative' kill
|
||||
if(stat.defender_plague) {
|
||||
chance_to_kill -= 1.0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
defhp += stat.amount_defender_drains;
|
||||
if(defhp > target_max_hp)
|
||||
defhp = target_max_hp;
|
||||
}
|
||||
|
||||
--defends;
|
||||
}
|
||||
}
|
||||
|
||||
if(defhp <= 0) {
|
||||
break;
|
||||
} else if(atthp == 0) {
|
||||
avg_losses += cost;
|
||||
} else if(map[movements[i].second.x][movements[i].second.y] ==
|
||||
gamemap::TOWER) {
|
||||
atthp += game_config::heal_amount;
|
||||
if(atthp > hitpoints[i])
|
||||
atthp = hitpoints[i];
|
||||
}
|
||||
|
||||
defenderxp += (atthp == 0 ? 10:1)*att->second.type().level();
|
||||
|
||||
avg_damage_taken += hitpoints[i] - atthp;
|
||||
}
|
||||
|
||||
//penalty for allowing advancement is a 'negative' kill
|
||||
if(defend_it->second.experience() < defend_it->second.max_experience()&&
|
||||
defend_it->second.experience() + defenderxp >=
|
||||
defend_it->second.max_experience()) {
|
||||
chance_to_kill -= 1.0;
|
||||
} else if(defhp == 0) {
|
||||
chance_to_kill += 1.0;
|
||||
} else if(map[defend_it->first.x][defend_it->first.y]==gamemap::TOWER) {
|
||||
defhp += game_config::heal_amount;
|
||||
if(defhp > target_hp)
|
||||
defhp = target_hp;
|
||||
}
|
||||
|
||||
avg_damage_inflicted += target_hp - defhp;
|
||||
}
|
||||
|
||||
chance_to_kill /= num_sims;
|
||||
avg_damage_inflicted /= num_sims;
|
||||
avg_damage_taken /= num_sims;
|
||||
terrain_quality /= resources_used;
|
||||
resources_used /= num_sims;
|
||||
avg_losses /= num_sims;
|
||||
}
|
||||
|
||||
double attack_analysis::rating(double aggression) const
|
||||
{
|
||||
double value = chance_to_kill*target_value - avg_losses;
|
||||
|
||||
//prefer to attack already damaged targets
|
||||
value += ((target_starting_damage/3 + avg_damage_inflicted)*
|
||||
(target_value/resources_used) -
|
||||
(1.0-aggression)*avg_damage_taken*(resources_used/target_value))/10.0;
|
||||
value /= ((resources_used/2) + (resources_used/2)*terrain_quality);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
std::vector<attack_analysis> analyze_targets(
|
||||
const gamemap& map,
|
||||
const std::multimap<location,location>& srcdst,
|
||||
const std::multimap<location,location>& dstsrc,
|
||||
std::map<location,unit>& units,
|
||||
const team& current_team, int team_num,
|
||||
const gamestatus& status, const game_data& data
|
||||
)
|
||||
{
|
||||
log_scope("analyzing targets...");
|
||||
|
||||
weapon_choice_cache.clear();
|
||||
|
||||
std::vector<attack_analysis> res;
|
||||
|
||||
std::vector<location> unit_locs;
|
||||
for(std::map<location,unit>::const_iterator i = units.begin();
|
||||
i != units.end(); ++i) {
|
||||
if(i->second.side() == team_num) {
|
||||
unit_locs.push_back(i->first);
|
||||
}
|
||||
}
|
||||
|
||||
bool used_locations[6];
|
||||
std::fill(used_locations,used_locations+6,false);
|
||||
|
||||
for(std::map<location,unit>::const_iterator j = units.begin();
|
||||
j != units.end(); ++j) {
|
||||
|
||||
//attack anyone who is on the enemy side, and who is not invisible
|
||||
if(current_team.is_enemy(j->second.side()) &&
|
||||
j->second.invisible(map.underlying_terrain(
|
||||
map[j->first.x][j->first.y])) == false) {
|
||||
location adjacent[6];
|
||||
get_adjacent_tiles(j->first,adjacent);
|
||||
attack_analysis analysis;
|
||||
analysis.target = j->first;
|
||||
|
||||
const int ticks = SDL_GetTicks();
|
||||
|
||||
do_analysis(map,j->first,srcdst,dstsrc,adjacent,used_locations,
|
||||
unit_locs,units,res,data,status,analysis);
|
||||
|
||||
const int time_taken = SDL_GetTicks() - ticks;
|
||||
static int max_time = 0;
|
||||
if(time_taken > max_time)
|
||||
max_time = time_taken;
|
||||
|
||||
std::cerr << "do_analysis took " << time_taken << " (" << max_time << ")\n";
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
77
src/ai_attack.hpp
Normal file
77
src/ai_attack.hpp
Normal file
|
@ -0,0 +1,77 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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 AI_ATTACK_INCLUDED
|
||||
#define AI_ATTACK_INCLUDED
|
||||
|
||||
#include "actions.hpp"
|
||||
#include "ai.hpp"
|
||||
#include "gamestatus.hpp"
|
||||
#include "map.hpp"
|
||||
#include "unit_types.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
namespace ai {
|
||||
|
||||
struct attack_analysis
|
||||
{
|
||||
void analyze(const gamemap& map, std::map<location,unit>& units,
|
||||
const gamestatus& status, const game_data& info, int sims);
|
||||
|
||||
double rating(double aggression) const;
|
||||
|
||||
gamemap::location target;
|
||||
std::vector<std::pair<gamemap::location,gamemap::location> > movements;
|
||||
std::vector<int> weapons;
|
||||
|
||||
//the value of the unit being targeted
|
||||
double target_value;
|
||||
|
||||
//the value on average, of units lost in the combat
|
||||
double avg_losses;
|
||||
|
||||
//estimated % chance to kill the unit
|
||||
double chance_to_kill;
|
||||
|
||||
//the average hitpoints damage inflicted
|
||||
double avg_damage_inflicted;
|
||||
|
||||
int target_starting_damage;
|
||||
|
||||
//the average hitpoints damage taken
|
||||
double avg_damage_taken;
|
||||
|
||||
//the sum of the values of units used in the attack
|
||||
double resources_used;
|
||||
|
||||
//the weighted average of the % chance to hit each attacking unit
|
||||
double terrain_quality;
|
||||
|
||||
//the ratio of the attacks the unit being attacked will get to
|
||||
//the strength of its most powerful attack
|
||||
double counter_strength_ratio;
|
||||
};
|
||||
|
||||
std::vector<attack_analysis> analyze_targets(
|
||||
const gamemap& map,
|
||||
const std::multimap<location,location>& srcdst,
|
||||
const std::multimap<location,location>& dstsrc,
|
||||
std::map<location,unit>& units,
|
||||
const team& current_team, int team_num,
|
||||
const gamestatus& status, const game_data& data
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
292
src/ai_move.cpp
Normal file
292
src/ai_move.cpp
Normal file
|
@ -0,0 +1,292 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "ai_move.hpp"
|
||||
#include "display.hpp"
|
||||
#include "game_config.hpp"
|
||||
#include "log.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace ai {
|
||||
|
||||
struct move_cost_calculator
|
||||
{
|
||||
move_cost_calculator(const unit& u, const gamemap& map,
|
||||
const game_data& data,
|
||||
const std::map<location,unit>& units,
|
||||
const gamemap::location& loc,
|
||||
const std::multimap<location,location>& dstsrc)
|
||||
: unit_(u), map_(map), data_(data), units_(units),
|
||||
move_type_(u.type().movement_type()), loc_(loc), dstsrc_(dstsrc)
|
||||
{}
|
||||
|
||||
double cost(const location& loc) const
|
||||
{
|
||||
if(!map_.on_board(loc))
|
||||
return 1000.0;
|
||||
|
||||
//if this unit can move to that location this turn, it has a very
|
||||
//very low cost
|
||||
typedef std::multimap<location,location>::const_iterator Itor;
|
||||
std::pair<Itor,Itor> range = dstsrc_.equal_range(loc);
|
||||
while(range.first != range.second) {
|
||||
if(range.first->first == loc)
|
||||
return 0.01;
|
||||
++range.first;
|
||||
}
|
||||
|
||||
const gamemap::TERRAIN terrain =
|
||||
map_.underlying_terrain(map_[loc.x][loc.y]);
|
||||
|
||||
const double modifier = 1.0;//move_type_.defense_modifier(map_,terrain);
|
||||
const double move_cost = move_type_.movement_cost(map_,terrain);
|
||||
|
||||
double enemies = 0;
|
||||
/* //is this stuff responsible for making it take a long time?
|
||||
location adj[7];
|
||||
adj[0] = loc;
|
||||
get_adjacent_tiles(loc,adj+1);
|
||||
for(int i = 0; i != 7; ++i) {
|
||||
const std::map<location,unit>::const_iterator en=units_.find(adj[i]);
|
||||
//at the moment, don't allow any units to be in the path
|
||||
if(i == 0 && en != units_.end()) {
|
||||
return 1000.0;
|
||||
}
|
||||
|
||||
if(en != units_.end() && en->second.side() == enemy_) {
|
||||
enemies += 1.0;
|
||||
}
|
||||
}
|
||||
*/
|
||||
const double res = modifier*move_cost + enemies*2.0;
|
||||
assert(res > 0);
|
||||
return res;
|
||||
}
|
||||
|
||||
private:
|
||||
const unit& unit_;
|
||||
const gamemap& map_;
|
||||
const game_data& data_;
|
||||
const std::map<location,unit>& units_;
|
||||
const unit_movement_type& move_type_;
|
||||
const gamemap::location loc_;
|
||||
const std::multimap<location,location> dstsrc_;
|
||||
|
||||
};
|
||||
|
||||
std::vector<target> find_targets(
|
||||
const gamemap& map, std::map<location,unit>& units,
|
||||
std::vector<team>& teams, int current_team
|
||||
)
|
||||
{
|
||||
log_scope("finding targets...");
|
||||
|
||||
team& tm = teams[current_team-1];
|
||||
|
||||
std::vector<target> targets;
|
||||
|
||||
if(tm.village_value() > 0.0) {
|
||||
const std::vector<location>& towers = map.towers();
|
||||
for(std::vector<location>::const_iterator t = towers.begin();
|
||||
t != towers.end(); ++t) {
|
||||
assert(map.on_board(*t));
|
||||
bool get_tower = true;
|
||||
for(int i = 0; i != teams.size(); ++i) {
|
||||
if(!tm.is_enemy(i+1) && teams[i].owns_tower(*t)) {
|
||||
get_tower = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(get_tower) {
|
||||
targets.push_back(target(*t,tm.village_value()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<team::target>& team_targets = tm.targets();
|
||||
|
||||
//find the enemy leaders and explicit targets
|
||||
std::map<location,unit>::const_iterator u;
|
||||
for(u = units.begin(); u != units.end(); ++u) {
|
||||
|
||||
//is an enemy leader
|
||||
if(u->second.can_recruit() && tm.is_enemy(u->second.side())) {
|
||||
assert(map.on_board(u->first));
|
||||
targets.push_back(target(u->first,tm.leader_value()));
|
||||
}
|
||||
|
||||
//explicit targets for this team
|
||||
for(std::vector<team::target>::iterator j = team_targets.begin();
|
||||
j != team_targets.end(); ++j) {
|
||||
if(u->second.matches_filter(j->criteria)) {
|
||||
std::cerr << "found explicit target..." << j->value << "\n";
|
||||
targets.push_back(target(u->first,j->value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(int n = 0; n != new_values.size(); ++n) {
|
||||
std::cerr << "target value: " << targets[n].value << " -> " << new_values[n] << "\n";
|
||||
targets[n].value = new_values[n];
|
||||
}
|
||||
|
||||
return targets;
|
||||
}
|
||||
|
||||
std::pair<location,location> choose_move(
|
||||
std::vector<target>& targets,
|
||||
const std::multimap<location,location>& dstsrc,
|
||||
std::map<location,unit>& units,
|
||||
const gamemap& map, const std::vector<team>& teams,
|
||||
int current_team,
|
||||
const game_data& data
|
||||
)
|
||||
{
|
||||
log_scope("choosing move");
|
||||
|
||||
std::vector<target>::const_iterator ittg;
|
||||
for(ittg = targets.begin(); ittg != targets.end(); ++ittg) {
|
||||
assert(map.on_board(ittg->loc));
|
||||
}
|
||||
|
||||
paths::route best_route;
|
||||
std::map<location,unit>::iterator best = units.end();
|
||||
double best_rating = 0.1;
|
||||
std::vector<target>::iterator best_target = targets.end();
|
||||
|
||||
std::map<location,unit>::iterator u;
|
||||
|
||||
//find the first eligible unit
|
||||
for(u = units.begin(); u != units.end(); ++u) {
|
||||
if(!(u->second.side() != current_team || u->second.can_recruit() ||
|
||||
u->second.movement_left() <= 0)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(u == units.end()) {
|
||||
std::cout << "no eligible units found\n";
|
||||
return std::pair<location,location>();
|
||||
}
|
||||
|
||||
//guardian units stay put
|
||||
if(u->second.is_guardian()) {
|
||||
std::cerr << u->second.type().name() << " is guardian, staying still\n";
|
||||
return std::pair<location,location>(u->first,u->first);
|
||||
}
|
||||
|
||||
const move_cost_calculator cost_calc(u->second,map,data,units,
|
||||
u->first,dstsrc);
|
||||
|
||||
//choose the best target for that unit
|
||||
for(std::vector<target>::iterator tg = targets.begin();
|
||||
tg != targets.end(); ++tg) {
|
||||
assert(map.on_board(tg->loc));
|
||||
const paths::route cur_route = a_star_search(u->first,tg->loc,
|
||||
minimum(tg->value/best_rating,100.0),cost_calc);
|
||||
const double rating = tg->value/cur_route.move_left;
|
||||
std::cerr << tg->value << "/" << cur_route.move_left << " = " << rating << "\n";
|
||||
if(best_target == targets.end() || rating > best_rating) {
|
||||
best_rating = rating;
|
||||
best_target = tg;
|
||||
best = u;
|
||||
best_route = cur_route;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(best_target == targets.end()) {
|
||||
std::cout << "no eligible targets found\n";
|
||||
return std::pair<location,location>();
|
||||
}
|
||||
|
||||
//now see if any other unit can put a better bid forward
|
||||
for(++u; u != units.end(); ++u) {
|
||||
if(u->second.side() != current_team || u->second.can_recruit() ||
|
||||
u->second.movement_left() <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const move_cost_calculator calc(u->second,map,data,units,
|
||||
u->first,dstsrc);
|
||||
const paths::route cur_route = a_star_search(u->first,best_target->loc,
|
||||
minimum(best_target->value/best_rating,100.0),calc);
|
||||
const double rating = best_target->value/cur_route.move_left;
|
||||
if(best == units.end() || rating > best_rating) {
|
||||
best_rating = rating;
|
||||
best = u;
|
||||
best_route = cur_route;
|
||||
}
|
||||
}
|
||||
|
||||
assert(best_target >= targets.begin() && best_target < targets.end());
|
||||
best_target->value -= best->second.type().cost()/20.0;
|
||||
if(best_target->value <= 0.0)
|
||||
targets.erase(best_target);
|
||||
|
||||
for(ittg = targets.begin();
|
||||
ittg != targets.end(); ++ittg) {
|
||||
assert(map.on_board(ittg->loc));
|
||||
}
|
||||
|
||||
for(std::vector<location>::reverse_iterator ri =
|
||||
best_route.steps.rbegin(); ri != best_route.steps.rend(); ++ri) {
|
||||
|
||||
if(game_config::debug) {
|
||||
display::debug_highlight(*ri,0.2);
|
||||
}
|
||||
|
||||
typedef std::multimap<location,location>::const_iterator Itor;
|
||||
std::pair<Itor,Itor> its = dstsrc.equal_range(*ri);
|
||||
while(its.first != its.second) {
|
||||
if(its.first->second == best->first) {
|
||||
return std::pair<location,location>(its.first->second,
|
||||
its.first->first);
|
||||
}
|
||||
|
||||
++its.first;
|
||||
}
|
||||
}
|
||||
|
||||
if(best != units.end()) {
|
||||
std::cout << "Could not make good move, staying still\n";
|
||||
return std::pair<location,location>(best->first,best->first);
|
||||
}
|
||||
|
||||
std::cout << "Could not find anywhere to move!\n";
|
||||
return std::pair<location,location>();
|
||||
}
|
||||
|
||||
}
|
50
src/ai_move.hpp
Normal file
50
src/ai_move.hpp
Normal file
|
@ -0,0 +1,50 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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 AI_MOVE_H_INCLUDED
|
||||
#define AI_MOVE_H_INCLUDED
|
||||
|
||||
#include "map.hpp"
|
||||
#include "pathfind.hpp"
|
||||
#include "unit.hpp"
|
||||
#include "unit_types.hpp"
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace ai {
|
||||
|
||||
typedef gamemap::location location;
|
||||
|
||||
struct target {
|
||||
target(const location& pos, double val) : loc(pos), value(val)
|
||||
{}
|
||||
location loc;
|
||||
double value;
|
||||
};
|
||||
|
||||
std::vector<target> find_targets(
|
||||
const gamemap& map, std::map<location,unit>& units,
|
||||
std::vector<team>& teams, int current_team
|
||||
);
|
||||
|
||||
std::pair<location,location> choose_move(
|
||||
std::vector<target>& targets,
|
||||
const std::multimap<location,location>& dstsrc,
|
||||
std::map<location,unit>& units,
|
||||
const gamemap& map, const std::vector<team>& teams,
|
||||
int current_team,
|
||||
const game_data& data
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
544
src/config.cpp
Normal file
544
src/config.cpp
Normal file
|
@ -0,0 +1,544 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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.
|
||||
*/
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <stack>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
#include "config.hpp"
|
||||
#include "filesystem.hpp"
|
||||
#include "log.hpp"
|
||||
|
||||
bool operator<(const line_source& a, const line_source& b)
|
||||
{
|
||||
return a.linenum < b.linenum;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
line_source get_line_source(const std::vector<line_source>& line_src, int line)
|
||||
{
|
||||
line_source res(line,"",0);
|
||||
std::vector<line_source>::const_iterator it =
|
||||
std::upper_bound(line_src.begin(),line_src.end(),res);
|
||||
if(it != line_src.begin()) {
|
||||
--it;
|
||||
res.file = it->file;
|
||||
res.fileline = it->fileline + (line - it->linenum);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::string read_file(const std::string& fname)
|
||||
{
|
||||
//if we have a path to the data
|
||||
#ifdef WESNOTH_PATH
|
||||
|
||||
//convert any filepath which is relative
|
||||
if(!fname.empty() && fname[0] != '/' && WESNOTH_PATH[0] == '/') {
|
||||
std::cerr << "trying to read file: '" <<
|
||||
(WESNOTH_PATH + std::string("/") + fname) << "'\n";
|
||||
const std::string& res =
|
||||
read_file(WESNOTH_PATH + std::string("/") + fname);
|
||||
if(!res.empty()) {
|
||||
std::cerr << "success\n";
|
||||
return res;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
std::ifstream file(fname.c_str());
|
||||
std::string res;
|
||||
char c;
|
||||
while(file.get(c)) {
|
||||
res.resize(res.size()+1);
|
||||
res[res.size()-1] = c;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void write_file(const std::string& fname, const std::string& data)
|
||||
{
|
||||
std::ofstream file(fname.c_str());
|
||||
for(std::string::const_iterator i = data.begin(); i != data.end(); ++i) {
|
||||
file << *i;
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
void internal_preprocess_file(const std::string& fname,
|
||||
std::map<std::string,std::string> defines_map,
|
||||
int depth, std::vector<char>& res,
|
||||
std::vector<line_source>* lines_src, int& line)
|
||||
{
|
||||
//if it's a directory, we process all files in the directory
|
||||
//that end in .cfg
|
||||
if(is_directory(fname)) {
|
||||
|
||||
std::vector<std::string> files;
|
||||
get_files_in_dir(fname,&files,NULL,ENTIRE_FILE_PATH);
|
||||
|
||||
for(std::vector<std::string>::const_iterator f = files.begin();
|
||||
f != files.end(); ++f) {
|
||||
if(f->size() > 4 && std::equal(f->end()-4,f->end(),".cfg")) {
|
||||
internal_preprocess_file(*f,defines_map,depth,res,
|
||||
lines_src,line);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int srcline = 1;
|
||||
|
||||
if(lines_src != NULL) {
|
||||
lines_src->push_back(line_source(line,fname,srcline));
|
||||
}
|
||||
|
||||
const std::string data = read_file(fname);
|
||||
|
||||
bool in_quotes = false;
|
||||
|
||||
for(std::string::const_iterator i = data.begin(); i != data.end(); ++i) {
|
||||
const char c = *i;
|
||||
if(c == '"') {
|
||||
in_quotes = !in_quotes;
|
||||
}
|
||||
|
||||
if(c == '{') {
|
||||
std::stringstream newfile;
|
||||
for(++i; i != data.end() && *i != '}'; ++i) {
|
||||
newfile << *i;
|
||||
}
|
||||
|
||||
if(i == data.end())
|
||||
break;
|
||||
|
||||
const std::string newfilename = newfile.str();
|
||||
|
||||
//if this is a known pre-processing symbol, then we insert
|
||||
//it, otherwise we assume it's a file name to load
|
||||
if(defines_map.count(newfilename) != 0) {
|
||||
const std::string& val = defines_map[newfilename];
|
||||
res.insert(res.end(),val.begin(),val.end());
|
||||
line += std::count(val.begin(),val.end(),'\n');
|
||||
} else if(depth < 20) {
|
||||
internal_preprocess_file("data/" + newfilename,
|
||||
defines_map, depth+1,res,
|
||||
lines_src,line);
|
||||
} else {
|
||||
const std::string& str = read_file(newfilename);
|
||||
res.insert(res.end(),str.begin(),str.end());
|
||||
line += std::count(str.begin(),str.end(),'\n');
|
||||
}
|
||||
|
||||
if(lines_src != NULL) {
|
||||
lines_src->push_back(line_source(line,fname,srcline));
|
||||
}
|
||||
} else if(c == '#' && !in_quotes) {
|
||||
//we are about to skip some things, so keep track of
|
||||
//the start of where we're skipping, so we can count
|
||||
//the number of newlines, so we can track the line number
|
||||
//in the source file
|
||||
const std::string::const_iterator begin = i;
|
||||
|
||||
//if this is the beginning of a pre-processing definition
|
||||
static const std::string hash_define("#define");
|
||||
if(data.end() - i > hash_define.size() &&
|
||||
std::equal(hash_define.begin(),hash_define.end(),i)) {
|
||||
|
||||
i += hash_define.size();
|
||||
while(i != data.end() && isspace(*i))
|
||||
++i;
|
||||
|
||||
const std::string::const_iterator end =
|
||||
std::find_if(i,data.end(),isspace);
|
||||
|
||||
if(end == data.end())
|
||||
break;
|
||||
|
||||
const std::string symbol(i,end);
|
||||
std::stringstream value;
|
||||
for(i = end+1; i != data.end(); ++i) {
|
||||
static const std::string hash_enddef("#enddef");
|
||||
if(data.end() - i > hash_enddef.size() &&
|
||||
std::equal(hash_enddef.begin(),hash_enddef.end(),i)) {
|
||||
i += hash_enddef.size();
|
||||
break;
|
||||
}
|
||||
|
||||
value << *i;
|
||||
}
|
||||
|
||||
defines_map.insert(std::pair<std::string,std::string>(
|
||||
symbol,value.str()));
|
||||
}
|
||||
|
||||
//if this is a pre-processing conditional
|
||||
static const std::string hash_ifdef("#ifdef");
|
||||
static const std::string hash_else("#else");
|
||||
static const std::string hash_endif("#endif");
|
||||
|
||||
if(data.end() - i > hash_ifdef.size() &&
|
||||
std::equal(hash_ifdef.begin(),hash_ifdef.end(),i)) {
|
||||
i += hash_ifdef.size();
|
||||
while(i != data.end() && isspace(*i))
|
||||
++i;
|
||||
|
||||
const std::string::const_iterator end =
|
||||
std::find_if(i,data.end(),isspace);
|
||||
|
||||
if(end == data.end())
|
||||
break;
|
||||
|
||||
//if the symbol is not defined, then we want to skip
|
||||
//to the #endif or #else . Otherwise, continue processing
|
||||
//as normal. The #endif will just be treated as a comment
|
||||
//anyway.
|
||||
const std::string symbol(i,end);
|
||||
if(defines_map.count(symbol) == 0) {
|
||||
while(data.end() - i > hash_endif.size() &&
|
||||
!std::equal(hash_endif.begin(),hash_endif.end(),i) &&
|
||||
!std::equal(hash_else.begin(),hash_else.end(),i)) {
|
||||
++i;
|
||||
}
|
||||
|
||||
i = std::find(i,data.end(),'\n');
|
||||
if(i == data.end())
|
||||
break;
|
||||
} else {
|
||||
i = end;
|
||||
}
|
||||
}
|
||||
|
||||
//if we come across a #else, it must mean that we found a #ifdef
|
||||
//earlier, and we should ignore until #endif
|
||||
if(data.end() - i > hash_else.size() &&
|
||||
std::equal(hash_else.begin(),hash_else.end(),i)) {
|
||||
while(data.end() - i > hash_endif.size() &&
|
||||
!std::equal(hash_endif.begin(),hash_endif.end(),i)) {
|
||||
++i;
|
||||
}
|
||||
|
||||
i = std::find(i,data.end(),'\n');
|
||||
if(i == data.end())
|
||||
break;
|
||||
}
|
||||
|
||||
for(; i != data.end() && *i != '\n'; ++i) {
|
||||
}
|
||||
|
||||
if(i == data.end())
|
||||
break;
|
||||
|
||||
srcline += std::count(begin,i,'\n');
|
||||
++line;
|
||||
|
||||
res.push_back('\n');
|
||||
} else {
|
||||
if(c == '\n') {
|
||||
++line;
|
||||
++srcline;
|
||||
}
|
||||
|
||||
res.push_back(c);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
} //end anonymous namespace
|
||||
|
||||
std::string preprocess_file(const std::string& fname,
|
||||
const std::map<std::string,std::string>* defines,
|
||||
std::vector<line_source>* line_sources)
|
||||
{
|
||||
log_scope("preprocessing file...");
|
||||
static const std::map<std::string,std::string> default_defines;
|
||||
if(defines == NULL)
|
||||
defines = &default_defines;
|
||||
|
||||
std::vector<char> res;
|
||||
int linenum = 0;
|
||||
internal_preprocess_file(fname,*defines,0,res,line_sources,linenum);
|
||||
return std::string(res.begin(),res.end());
|
||||
}
|
||||
|
||||
config::config(const std::string& data,
|
||||
const std::vector<line_source>* line_sources)
|
||||
{
|
||||
log_scope("parsing config...");
|
||||
read(data,line_sources);
|
||||
}
|
||||
|
||||
config::config(const config& cfg) : values(cfg.values)
|
||||
{
|
||||
for(std::map<std::string,std::vector<config*> >::const_iterator i =
|
||||
cfg.children.begin(); i != cfg.children.end(); ++i) {
|
||||
std::vector<config*> v;
|
||||
for(std::vector<config*>::const_iterator j = i->second.begin();
|
||||
j != i->second.end(); ++j) {
|
||||
v.push_back(new config(**j));
|
||||
}
|
||||
|
||||
children[i->first].swap(v);
|
||||
}
|
||||
}
|
||||
|
||||
config::~config()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
config& config::operator=(const config& cfg)
|
||||
{
|
||||
clear();
|
||||
|
||||
values = cfg.values;
|
||||
|
||||
for(std::map<std::string,std::vector<config*> >::const_iterator i =
|
||||
cfg.children.begin(); i != cfg.children.end(); ++i) {
|
||||
std::vector<config*> v;
|
||||
for(std::vector<config*>::const_iterator j = i->second.begin();
|
||||
j != i->second.end(); ++j) {
|
||||
v.push_back(new config(**j));
|
||||
}
|
||||
|
||||
children[i->first].swap(v);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void config::read(const std::string& data,
|
||||
const std::vector<line_source>* line_sources)
|
||||
{
|
||||
clear();
|
||||
|
||||
std::stack<std::string> element_names;
|
||||
std::stack<config*> elements;
|
||||
elements.push(this);
|
||||
element_names.push("");
|
||||
|
||||
enum { ELEMENT_NAME, IN_ELEMENT, VARIABLE_NAME, VALUE }
|
||||
state = IN_ELEMENT;
|
||||
std::string var;
|
||||
std::string value;
|
||||
|
||||
bool in_quotes = false;
|
||||
|
||||
int line = 0;
|
||||
|
||||
for(std::string::const_iterator i = data.begin(); i != data.end(); ++i) {
|
||||
const char c = *i;
|
||||
if(c == '\n')
|
||||
++line;
|
||||
|
||||
switch(state) {
|
||||
case ELEMENT_NAME:
|
||||
if(c == ']') {
|
||||
if(value == "end" || !value.empty() && value[0] == '/') {
|
||||
assert(!elements.empty());
|
||||
|
||||
if(value[0] == '/' &&
|
||||
std::string("/" + element_names.top()) != value) {
|
||||
std::stringstream err;
|
||||
|
||||
if(line_sources != NULL) {
|
||||
const line_source src =
|
||||
get_line_source(*line_sources,line);
|
||||
|
||||
err << src.file << " " << src.fileline << ": ";
|
||||
}
|
||||
|
||||
err << "Found illegal end tag: '" << value
|
||||
<< "', at end of '"
|
||||
<< element_names.top() << "'";
|
||||
|
||||
throw error(err.str());
|
||||
}
|
||||
|
||||
elements.pop();
|
||||
element_names.pop();
|
||||
|
||||
if(elements.empty()) {
|
||||
std::stringstream err;
|
||||
|
||||
if(line_sources != NULL) {
|
||||
const line_source src =
|
||||
get_line_source(*line_sources,line);
|
||||
|
||||
err << src.file << " " << src.fileline << ": ";
|
||||
}
|
||||
|
||||
err << "Unexpected terminating tag\n";
|
||||
throw error(err.str());
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
state = IN_ELEMENT;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
config* const new_config = new config();
|
||||
elements.top()->children[value].push_back(new_config);
|
||||
elements.push(new_config);
|
||||
element_names.push(value);
|
||||
state = IN_ELEMENT;
|
||||
value = "";
|
||||
} else {
|
||||
value.resize(value.size()+1);
|
||||
value[value.size()-1] = c;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case IN_ELEMENT:
|
||||
if(c == '[') {
|
||||
state = ELEMENT_NAME;
|
||||
value = "";
|
||||
} else if(!isspace(c)) {
|
||||
value.resize(1);
|
||||
value[0] = c;
|
||||
state = VARIABLE_NAME;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case VARIABLE_NAME:
|
||||
if(c == '=') {
|
||||
state = VALUE;
|
||||
var = value;
|
||||
value = "";
|
||||
} else {
|
||||
value.resize(value.size()+1);
|
||||
value[value.size()-1] = c;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case VALUE:
|
||||
if(c == '"') {
|
||||
in_quotes = !in_quotes;
|
||||
} else if(c == '\n' && !in_quotes) {
|
||||
state = IN_ELEMENT;
|
||||
elements.top()->values.insert(
|
||||
std::pair<std::string,std::string>(var,value));
|
||||
var = "";
|
||||
value = "";
|
||||
} else {
|
||||
value.resize(value.size()+1);
|
||||
value[value.size()-1] = c;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string config::write() const
|
||||
{
|
||||
std::string res;
|
||||
for(std::map<std::string,std::string>::const_iterator i = values.begin();
|
||||
i != values.end(); ++i) {
|
||||
if(i->second.empty() == false) {
|
||||
res += i->first + "=" + i->second + "\n";
|
||||
}
|
||||
}
|
||||
|
||||
for(std::map<std::string,std::vector<config*> >::const_iterator j =
|
||||
children.begin(); j != children.end(); ++j) {
|
||||
const std::vector<config*>& v = j->second;
|
||||
for(std::vector<config*>::const_iterator it = v.begin();
|
||||
it != v.end(); ++it) {
|
||||
res += "[" + j->first + "]\n";
|
||||
res += (*it)->write();
|
||||
res += "[/" + j->first + "]\n";
|
||||
}
|
||||
}
|
||||
|
||||
res += "\n";
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::vector<std::string> config::split(const std::string& val)
|
||||
{
|
||||
std::vector<std::string> res;
|
||||
|
||||
std::string::const_iterator i1 = val.begin();
|
||||
std::string::const_iterator i2 = val.begin();
|
||||
|
||||
while(i2 != val.end()) {
|
||||
if(*i2 == ',') {
|
||||
std::string new_val(i1,i2);
|
||||
if(!new_val.empty())
|
||||
res.push_back(new_val);
|
||||
++i2;
|
||||
while(i2 != val.end() && *i2 == ' ')
|
||||
++i2;
|
||||
|
||||
i1 = i2;
|
||||
} else {
|
||||
++i2;
|
||||
}
|
||||
}
|
||||
|
||||
std::string new_val(i1,i2);
|
||||
if(!new_val.empty())
|
||||
res.push_back(new_val);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool config::has_value(const std::string& values, const std::string& val)
|
||||
{
|
||||
const std::vector<std::string>& vals = split(values);
|
||||
return std::count(vals.begin(),vals.end(),val) > 0;
|
||||
}
|
||||
|
||||
void config::clear()
|
||||
{
|
||||
for(std::map<std::string,std::vector<config*> >::iterator i =
|
||||
children.begin(); i != children.end(); ++i) {
|
||||
std::vector<config*>& v = i->second;
|
||||
for(std::vector<config*>::iterator j = v.begin(); j != v.end(); ++j)
|
||||
delete *j;
|
||||
}
|
||||
|
||||
children.clear();
|
||||
values.clear();
|
||||
}
|
||||
|
||||
//#define TEST_CONFIG
|
||||
|
||||
#ifdef TEST_CONFIG
|
||||
|
||||
int main()
|
||||
{
|
||||
config cfg(read_file("testconfig"));
|
||||
std::cout << cfg.write() << std::endl;
|
||||
}
|
||||
|
||||
#endif
|
69
src/config.hpp
Normal file
69
src/config.hpp
Normal file
|
@ -0,0 +1,69 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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 CONFIG_HPP_INCLUDED
|
||||
#define CONFIG_HPP_INCLUDED
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct line_source
|
||||
{
|
||||
line_source(int ln,const std::string& fname, int line) :
|
||||
linenum(ln), file(fname), fileline(line)
|
||||
{}
|
||||
|
||||
int linenum;
|
||||
std::string file;
|
||||
int fileline;
|
||||
};
|
||||
|
||||
bool operator<(const line_source& a, const line_source& b);
|
||||
|
||||
std::string read_file(const std::string& fname);
|
||||
void write_file(const std::string& fname, const std::string& data);
|
||||
std::string preprocess_file(const std::string& fname,
|
||||
const std::map<std::string,std::string>* defines=0,
|
||||
std::vector<line_source>* src=0);
|
||||
|
||||
typedef std::map<std::string,std::string> string_map;
|
||||
|
||||
struct config
|
||||
{
|
||||
config() {}
|
||||
config(const std::string& data,
|
||||
const std::vector<line_source>* lines=0); //throws config::error
|
||||
config(const config& cfg);
|
||||
~config();
|
||||
|
||||
config& operator=(const config& cfg);
|
||||
|
||||
void read(const std::string& data,
|
||||
const std::vector<line_source>* lines=0); //throws config::error
|
||||
std::string write() const;
|
||||
|
||||
std::map<std::string,std::string> values;
|
||||
std::map<std::string,std::vector<config*> > children;
|
||||
|
||||
static std::vector<std::string> split(const std::string& val);
|
||||
static bool has_value(const std::string& values, const std::string& val);
|
||||
|
||||
void clear();
|
||||
|
||||
struct error {
|
||||
error(const std::string& msg) : message(msg) {}
|
||||
std::string message;
|
||||
};
|
||||
};
|
||||
|
||||
#endif
|
83
src/dialogs.cpp
Normal file
83
src/dialogs.cpp
Normal file
|
@ -0,0 +1,83 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "dialogs.hpp"
|
||||
#include "language.hpp"
|
||||
#include "replay.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace dialogs
|
||||
{
|
||||
|
||||
void advance_unit(const game_data& info,
|
||||
std::map<gamemap::location,unit>& units,
|
||||
const gamemap::location& loc,
|
||||
display& gui, bool random_choice)
|
||||
{
|
||||
std::map<gamemap::location,unit>::iterator u = units.find(loc);
|
||||
if(u == units.end() || u->second.advances() == false)
|
||||
return;
|
||||
|
||||
const std::vector<std::string>& options = u->second.type().advances_to();
|
||||
|
||||
std::vector<unit> sample_units;
|
||||
for(std::vector<std::string>::const_iterator op = options.begin();
|
||||
op != options.end(); ++op) {
|
||||
sample_units.push_back(::get_advanced_unit(info,units,loc,*op));
|
||||
}
|
||||
|
||||
int res = 0;
|
||||
|
||||
if(options.empty()) {
|
||||
return;
|
||||
} else if(random_choice) {
|
||||
res = rand()%options.size();
|
||||
} else if(options.size() > 1) {
|
||||
|
||||
res = gui::show_dialog(gui,NULL,string_table["advance_unit_heading"],
|
||||
string_table["advance_unit_message"],
|
||||
gui::OK_ONLY, &options, &sample_units);
|
||||
}
|
||||
|
||||
//when the unit advances, it fades to white, and then switches to the
|
||||
//new unit, then fades back to the normal colour
|
||||
double intensity;
|
||||
for(intensity = 1.0; intensity >= 0.0; intensity -= 0.05) {
|
||||
gui.set_advancing_unit(loc,intensity);
|
||||
gui.draw(false);
|
||||
gui.update_display();
|
||||
SDL_Delay(30);
|
||||
}
|
||||
|
||||
recorder.choose_option(res);
|
||||
|
||||
::advance_unit(info,units,loc,options[res]);
|
||||
|
||||
gui.invalidate_unit();
|
||||
|
||||
for(intensity = 0.0; intensity <= 1.0; intensity += 0.05) {
|
||||
gui.set_advancing_unit(loc,intensity);
|
||||
gui.draw(false);
|
||||
gui.update_display();
|
||||
SDL_Delay(30);
|
||||
}
|
||||
|
||||
gui.set_advancing_unit(gamemap::location(),0.0);
|
||||
|
||||
gui.invalidate_all();
|
||||
gui.draw();
|
||||
}
|
||||
|
||||
} //end namespace dialogs
|
28
src/dialogs.hpp
Normal file
28
src/dialogs.hpp
Normal file
|
@ -0,0 +1,28 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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 DIALOGS_H_INCLUDED
|
||||
#define DIALOGS_H_INCLUDED
|
||||
|
||||
#include "actions.hpp"
|
||||
#include "display.hpp"
|
||||
#include "menu.hpp"
|
||||
|
||||
namespace dialogs
|
||||
{
|
||||
void advance_unit(const game_data& info,
|
||||
std::map<gamemap::location,unit>& units,
|
||||
const gamemap::location& loc,
|
||||
display& gui, bool random_choice=false);
|
||||
}
|
||||
|
||||
#endif
|
2275
src/display.cpp
Normal file
2275
src/display.cpp
Normal file
File diff suppressed because it is too large
Load diff
252
src/display.hpp
Normal file
252
src/display.hpp
Normal file
|
@ -0,0 +1,252 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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 DISPLAY_H_INCLUDED
|
||||
#define DISPLAY_H_INCLUDED
|
||||
|
||||
#include "gamestatus.hpp"
|
||||
#include "key.hpp"
|
||||
#include "map.hpp"
|
||||
#include "pathfind.hpp"
|
||||
#include "team.hpp"
|
||||
#include "unit.hpp"
|
||||
#include "video.hpp"
|
||||
|
||||
#include "SDL.h"
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
class display
|
||||
{
|
||||
public:
|
||||
typedef std::map<gamemap::location,unit> unit_map;
|
||||
typedef short Pixel;
|
||||
|
||||
display(unit_map& units, CVideo& video,
|
||||
const gamemap& map, const gamestatus& status,
|
||||
const std::vector<team>& t);
|
||||
~display();
|
||||
|
||||
Pixel rgb(int r, int g, int b) const;
|
||||
|
||||
void scroll(double xmov, double ymov);
|
||||
void zoom(double amount);
|
||||
void default_zoom();
|
||||
|
||||
enum SCROLL_TYPE { SCROLL, WARP };
|
||||
void scroll_to_tile(int x, int y, SCROLL_TYPE scroll_type=SCROLL);
|
||||
void scroll_to_tiles(int x1, int y1, int x2, int y2,
|
||||
SCROLL_TYPE scroll_type=SCROLL);
|
||||
|
||||
void redraw_everything();
|
||||
void draw(bool update=true,bool force=false);
|
||||
|
||||
int x() const;
|
||||
int mapx() const;
|
||||
int y() const;
|
||||
|
||||
void select_hex(gamemap::location hex);
|
||||
void highlight_hex(gamemap::location hex);
|
||||
gamemap::location hex_clicked_on(int x, int y);
|
||||
gamemap::location minimap_location_on(int x, int y);
|
||||
|
||||
void set_paths(const paths* paths_list);
|
||||
|
||||
double get_location_x(const gamemap::location& loc) const;
|
||||
double get_location_y(const gamemap::location& loc) const;
|
||||
|
||||
void move_unit(const std::vector<gamemap::location>& path, unit& u);
|
||||
bool unit_attack(const gamemap::location& a, const gamemap::location& b,
|
||||
int damage, const attack_type& attack);
|
||||
void draw_tile(int x, int y, SDL_Surface* unit_image=NULL,
|
||||
double alpha=1.0, short blend_to=0);
|
||||
|
||||
CVideo& video() { return screen_; }
|
||||
|
||||
enum IMAGE_TYPE { UNSCALED, SCALED, GREYED, BRIGHTENED };
|
||||
SDL_Surface* getImage(const std::string& filename,IMAGE_TYPE type=SCALED);
|
||||
|
||||
//blits a surface with black as alpha
|
||||
void blit_surface(int x, int y, SDL_Surface* surface);
|
||||
|
||||
void invalidate_all();
|
||||
void invalidate_game_status();
|
||||
void invalidate_unit();
|
||||
|
||||
void recalculate_minimap();
|
||||
|
||||
void add_overlay(const gamemap::location& loc, const std::string& image);
|
||||
void remove_overlay(const gamemap::location& loc);
|
||||
|
||||
void draw_unit_details(int x, int y, const gamemap::location& loc,
|
||||
const unit& u, SDL_Rect& description_rect, SDL_Rect& profile_rect);
|
||||
|
||||
void update_display();
|
||||
void update_rect(const SDL_Rect& rect);
|
||||
|
||||
void draw_terrain_palette(int x, int y, gamemap::TERRAIN selected);
|
||||
gamemap::TERRAIN get_terrain_on(int palx, int paly, int x, int y);
|
||||
|
||||
void set_team(int team);
|
||||
|
||||
void set_advancing_unit(const gamemap::location& loc, double amount);
|
||||
|
||||
void lock_updates(bool value);
|
||||
bool update_locked() const;
|
||||
|
||||
bool turbo() const;
|
||||
void set_turbo(bool turbo);
|
||||
|
||||
void set_grid(bool grid);
|
||||
|
||||
static void debug_highlight(const gamemap::location& loc, double amount);
|
||||
static void clear_debug_highlights();
|
||||
|
||||
private:
|
||||
display(const display&);
|
||||
void operator=(const display&);
|
||||
|
||||
void move_unit_between(const gamemap::location& a,
|
||||
const gamemap::location& b,
|
||||
const unit& u);
|
||||
|
||||
void draw_unit(int x, int y, const SDL_Surface* image,
|
||||
bool reverse, bool upside_down=false,
|
||||
double alpha=1.0, short blendto=0);
|
||||
|
||||
void unit_die(const gamemap::location& loc, SDL_Surface* image=NULL);
|
||||
|
||||
bool unit_attack_ranged(const gamemap::location& a,
|
||||
const gamemap::location& b,
|
||||
int damage, const attack_type& attack);
|
||||
|
||||
void draw_sidebar();
|
||||
SDL_Rect get_minimap_location(int x, int y, int w, int h);
|
||||
void draw_minimap(int x, int y, int w, int h);
|
||||
void draw_game_status(int x, int y);
|
||||
|
||||
SDL_Rect gameStatusRect_;
|
||||
SDL_Rect unitDescriptionRect_;
|
||||
SDL_Rect unitProfileRect_;
|
||||
|
||||
int lastTimeOfDay_;
|
||||
|
||||
void bounds_check_position();
|
||||
|
||||
std::vector<SDL_Surface*> getAdjacentTerrain(int x, int y, IMAGE_TYPE type);
|
||||
SDL_Surface* getTerrain(gamemap::TERRAIN, IMAGE_TYPE type,
|
||||
int x, int y, const std::string& dir="");
|
||||
|
||||
enum TINT { GREY_IMAGE, BRIGHTEN_IMAGE };
|
||||
SDL_Surface* getImageTinted(const std::string& filename, TINT tint);
|
||||
SDL_Surface* getMinimap(int w, int h);
|
||||
|
||||
void clearImageCache();
|
||||
|
||||
CVideo& screen_;
|
||||
mutable CKey keys_;
|
||||
double xpos_, ypos_, zoom_;
|
||||
const gamemap& map_;
|
||||
|
||||
gamemap::location selectedHex_;
|
||||
gamemap::location mouseoverHex_;
|
||||
|
||||
unit_map& units_;
|
||||
|
||||
std::map<std::string,SDL_Surface*> images_, scaledImages_,
|
||||
greyedImages_, brightenedImages_;
|
||||
|
||||
//function which finds the start and end rows on the energy bar image
|
||||
//where white pixels are substituted for the colour of the energy
|
||||
const std::pair<int,int>& calculate_energy_bar();
|
||||
std::pair<int,int> energy_bar_count_;
|
||||
|
||||
SDL_Surface* minimap_;
|
||||
bool minimapDecorationsDrawn_;
|
||||
|
||||
const paths* pathsList_;
|
||||
|
||||
const gamestatus& status_;
|
||||
|
||||
const std::vector<team>& teams_;
|
||||
|
||||
int lastDraw_;
|
||||
int drawSkips_;
|
||||
|
||||
void invalidate(const gamemap::location& loc);
|
||||
|
||||
std::set<gamemap::location> invalidated_;
|
||||
bool invalidateAll_;
|
||||
bool invalidateUnit_;
|
||||
bool invalidateGameStatus_;
|
||||
|
||||
std::multimap<gamemap::location,std::string> overlays_;
|
||||
|
||||
void update_whole_screen();
|
||||
void update_map_area();
|
||||
void update_side_bar();
|
||||
std::vector<SDL_Rect> updateRects_;
|
||||
|
||||
bool sideBarBgDrawn_;
|
||||
|
||||
int currentTeam_;
|
||||
|
||||
//used to store a unit that is not drawn, because it's currently
|
||||
//being moved or otherwise changed
|
||||
gamemap::location hiddenUnit_;
|
||||
|
||||
//used to store any unit that is currently being hit
|
||||
gamemap::location hitUnit_;
|
||||
|
||||
//used to store any unit that is dying
|
||||
gamemap::location deadUnit_;
|
||||
double deadAmount_;
|
||||
|
||||
//used to store any unit that is advancing
|
||||
gamemap::location advancingUnit_;
|
||||
double advancingAmount_;
|
||||
|
||||
int updatesLocked_;
|
||||
|
||||
bool turbo_, grid_;
|
||||
|
||||
//for debug mode
|
||||
static std::map<gamemap::location,double> debugHighlights_;
|
||||
};
|
||||
|
||||
struct update_locker
|
||||
{
|
||||
update_locker(display& d, bool lock=true) : disp(d), unlock(lock) {
|
||||
if(lock) {
|
||||
disp.lock_updates(true);
|
||||
}
|
||||
}
|
||||
|
||||
~update_locker() {
|
||||
unlock_update();
|
||||
}
|
||||
|
||||
void unlock_update() {
|
||||
if(unlock) {
|
||||
disp.lock_updates(false);
|
||||
unlock = false;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
display& disp;
|
||||
bool unlock;
|
||||
};
|
||||
|
||||
#endif
|
248
src/filesystem.cpp
Normal file
248
src/filesystem.cpp
Normal file
|
@ -0,0 +1,248 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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.
|
||||
*/
|
||||
//include files for opendir(3), readdir(3), etc. These files may vary
|
||||
//from platform to platform, since these functions are NOT ANSI-conforming
|
||||
//functions. They may have to be altered to port to new platforms
|
||||
#include <sys/types.h>
|
||||
#include <dirent.h>
|
||||
|
||||
//for mkdir
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#include <direct.h>
|
||||
|
||||
#include <io.h>
|
||||
|
||||
#define mkdir(a,b) (_mkdir(a))
|
||||
|
||||
#define mode_t int
|
||||
|
||||
#endif
|
||||
|
||||
//for getenv
|
||||
#include <cstdlib>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
|
||||
#include "filesystem.hpp"
|
||||
|
||||
namespace {
|
||||
const mode_t AccessMode = 00770;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
#define DIR_INVALID(d) (d == -1)
|
||||
#else
|
||||
#define DIR_INVALID(d) (d == NULL)
|
||||
#endif
|
||||
|
||||
void get_files_in_dir(const std::string& directory,
|
||||
std::vector<std::string>* files,
|
||||
std::vector<std::string>* dirs,
|
||||
FILE_NAME_MODE mode)
|
||||
{
|
||||
|
||||
//if we have a path to find directories in, then convert relative
|
||||
//pathnames to be rooted on the wesnoth path
|
||||
#ifdef WESNOTH_PATH
|
||||
if(!directory.empty() && directory[0] != '/' && WESNOTH_PATH[0] == '/') {
|
||||
const std::string& dir = WESNOTH_PATH + std::string("/") + directory;
|
||||
if(is_directory(dir)) {
|
||||
get_files_in_dir(dir,files,dirs,mode);
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
_finddata_t fileinfo;
|
||||
long dir = _findfirst((directory + "/*.*").c_str(),&fileinfo);
|
||||
#else
|
||||
|
||||
DIR* dir = opendir(directory.c_str());
|
||||
#endif
|
||||
|
||||
if(DIR_INVALID(dir)) {
|
||||
//try to make the directory
|
||||
const int res = mkdir(directory.c_str(),AccessMode);
|
||||
if(res == 0) {
|
||||
#ifdef _WIN32
|
||||
dir = _findfirst((directory + "/*.*").c_str(),&fileinfo);
|
||||
#else
|
||||
dir = opendir(directory.c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
if(DIR_INVALID(dir))
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
int res = dir;
|
||||
do {
|
||||
if(fileinfo.name[0] != '.') {
|
||||
const std::string path = (mode == ENTIRE_FILE_PATH ?
|
||||
directory + "/" : std::string("")) + fileinfo.name;
|
||||
|
||||
if(fileinfo.attrib&_A_SUBDIR) {
|
||||
if(dirs != NULL)
|
||||
dirs->push_back(path);
|
||||
} else {
|
||||
if(files != NULL)
|
||||
files->push_back(path);
|
||||
}
|
||||
}
|
||||
|
||||
res = _findnext(dir,&fileinfo);
|
||||
} while(!DIR_INVALID(res));
|
||||
|
||||
_findclose(dir);
|
||||
|
||||
#else
|
||||
struct dirent* entry;
|
||||
while((entry = readdir(dir)) != NULL) {
|
||||
if(entry->d_name[0] == '.')
|
||||
continue;
|
||||
|
||||
const std::string name((directory + "/") + entry->d_name);
|
||||
|
||||
//try to open it as a directory to test if it is a directory
|
||||
DIR* const temp_dir = opendir(name.c_str());
|
||||
if(temp_dir != NULL) {
|
||||
closedir(temp_dir);
|
||||
if(dirs != NULL) {
|
||||
if(mode == ENTIRE_FILE_PATH)
|
||||
dirs->push_back(name);
|
||||
else
|
||||
dirs->push_back(entry->d_name);
|
||||
}
|
||||
} else if(files != NULL) {
|
||||
if(mode == ENTIRE_FILE_PATH)
|
||||
files->push_back(name);
|
||||
else
|
||||
files->push_back(entry->d_name);
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
#endif
|
||||
|
||||
if(files != NULL)
|
||||
std::sort(files->begin(),files->end());
|
||||
|
||||
if(dirs != NULL)
|
||||
std::sort(dirs->begin(),dirs->end());
|
||||
}
|
||||
|
||||
std::string get_prefs_file()
|
||||
{
|
||||
return get_user_data_dir() + "/preferences";
|
||||
}
|
||||
|
||||
std::string get_saves_dir()
|
||||
{
|
||||
const std::string dir_path = get_user_data_dir() + "/saves";
|
||||
|
||||
#ifdef _WIN32
|
||||
_mkdir(dir_path.c_str());
|
||||
#else
|
||||
|
||||
DIR* dir = opendir(dir_path.c_str());
|
||||
if(dir == NULL) {
|
||||
const int res = mkdir(dir_path.c_str(),AccessMode);
|
||||
if(res == 0) {
|
||||
dir = opendir(dir_path.c_str());
|
||||
} else {
|
||||
std::cerr << "Could not open or create directory: '" << dir_path
|
||||
<< "'\n";
|
||||
}
|
||||
}
|
||||
|
||||
if(dir == NULL)
|
||||
return "";
|
||||
#endif
|
||||
|
||||
return dir_path;
|
||||
}
|
||||
|
||||
std::string get_user_data_dir()
|
||||
{
|
||||
static const char* const current_dir = ".";
|
||||
|
||||
#ifdef _WIN32
|
||||
_mkdir("userdata");
|
||||
return "userdata";
|
||||
#else
|
||||
|
||||
const char* home_str = getenv("HOME");
|
||||
if(home_str == NULL)
|
||||
home_str = current_dir;
|
||||
|
||||
const std::string home(home_str);
|
||||
|
||||
const std::string dir_path = home + "/.wesnoth";
|
||||
DIR* dir = opendir(dir_path.c_str());
|
||||
if(dir == NULL) {
|
||||
const int res = mkdir(dir_path.c_str(),AccessMode);
|
||||
if(res == 0) {
|
||||
dir = opendir(dir_path.c_str());
|
||||
} else {
|
||||
std::cerr << "Could not open or create directory: '" << dir_path
|
||||
<< "'\n";
|
||||
}
|
||||
}
|
||||
|
||||
if(dir == NULL)
|
||||
return "";
|
||||
|
||||
closedir(dir);
|
||||
|
||||
return dir_path;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool is_directory(const std::string& fname)
|
||||
{
|
||||
|
||||
#ifdef WESNOTH_PATH
|
||||
if(!fname.empty() && fname[0] != '/' && WESNOTH_PATH[0] == '/') {
|
||||
if(is_directory(WESNOTH_PATH + std::string("/") + fname))
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
_finddata_t info;
|
||||
const long handle = _findfirst((fname + "/*").c_str(),&info);
|
||||
if(handle >= 0) {
|
||||
_findclose(handle);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
#else
|
||||
DIR* const dir = opendir(fname.c_str());
|
||||
if(dir != NULL) {
|
||||
closedir(dir);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
32
src/filesystem.hpp
Normal file
32
src/filesystem.hpp
Normal file
|
@ -0,0 +1,32 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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 FILESYSTEM_HPP_INCLUDED
|
||||
#define FILESYSTEM_HPP_INCLUDED
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
enum FILE_NAME_MODE { ENTIRE_FILE_PATH, FILE_NAME_ONLY };
|
||||
|
||||
void get_files_in_dir(const std::string& dir,
|
||||
std::vector<std::string>* files,
|
||||
std::vector<std::string>* dirs=NULL,
|
||||
FILE_NAME_MODE mode=FILE_NAME_ONLY);
|
||||
|
||||
std::string get_prefs_file();
|
||||
std::string get_saves_dir();
|
||||
std::string get_user_data_dir();
|
||||
|
||||
bool is_directory(const std::string& fname);
|
||||
|
||||
#endif
|
199
src/font.cpp
Normal file
199
src/font.cpp
Normal file
|
@ -0,0 +1,199 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "config.hpp"
|
||||
#include "font.hpp"
|
||||
#include "SDL_ttf.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
|
||||
std::map<int,TTF_Font*> font_table;
|
||||
|
||||
//SDL_ttf seems to have a problem where TTF_OpenFont will seg fault if
|
||||
//the font file doesn't exist, so make sure it exists first.
|
||||
TTF_Font* open_font(const std::string& fname, int size)
|
||||
{
|
||||
if(read_file(fname).empty())
|
||||
return NULL;
|
||||
|
||||
return TTF_OpenFont(fname.c_str(),size);
|
||||
}
|
||||
|
||||
TTF_Font* get_font(int size)
|
||||
{
|
||||
const std::map<int,TTF_Font*>::iterator it = font_table.find(size);
|
||||
if(it != font_table.end())
|
||||
return it->second;
|
||||
|
||||
TTF_Font* font = NULL;
|
||||
std::cerr << "opening font file...\n";
|
||||
|
||||
#ifdef WESNOTH_PATH
|
||||
font = open_font(std::string(WESNOTH_PATH) + "/images/misc/Vera.ttf",size);
|
||||
#endif
|
||||
|
||||
if(font == NULL) {
|
||||
font = open_font("images/misc/Vera.ttf",size);
|
||||
}
|
||||
|
||||
if(font == NULL) {
|
||||
std::cerr << "Could not open font file\n";
|
||||
return font;
|
||||
}
|
||||
|
||||
TTF_SetFontStyle(font,TTF_STYLE_NORMAL);
|
||||
|
||||
std::cerr << "inserting font...\n";
|
||||
font_table.insert(std::pair<int,TTF_Font*>(size,font));
|
||||
return font;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace font {
|
||||
|
||||
manager::manager()
|
||||
{
|
||||
const int res = TTF_Init();
|
||||
if(res < 0) {
|
||||
std::cerr << "Could not initialize true type fonts\n";
|
||||
} else {
|
||||
std::cerr << "Initialized true type fonts\n";
|
||||
}
|
||||
}
|
||||
|
||||
manager::~manager()
|
||||
{
|
||||
for(std::map<int,TTF_Font*>::iterator i = font_table.begin();
|
||||
i != font_table.end(); ++i) {
|
||||
TTF_CloseFont(i->second);
|
||||
}
|
||||
|
||||
TTF_Quit();
|
||||
}
|
||||
|
||||
SDL_Rect draw_text_line(display* gui, const SDL_Rect& area, int size,
|
||||
COLOUR colour, const std::string& text, int x, int y,
|
||||
SDL_Surface* bg)
|
||||
{
|
||||
static const SDL_Color colours[] =
|
||||
// neutral good bad
|
||||
{ {0xFF,0xFF,0,0}, {0,0xFF,0,0}, {0xFF,0,0,0} };
|
||||
|
||||
const SDL_Color& col = colours[colour];
|
||||
TTF_Font* const font = get_font(size);
|
||||
if(font == NULL) {
|
||||
std::cerr << "Could not get font\n";
|
||||
SDL_Rect res;
|
||||
res.x = 0; res.y = 0; res.w = 0; res.h = 0;
|
||||
return res;
|
||||
}
|
||||
|
||||
SDL_Surface* const surface = TTF_RenderText_Blended(font,text.c_str(),col);
|
||||
if(surface == NULL) {
|
||||
std::cerr << "Could not render ttf: '" << text << "'\n";
|
||||
SDL_Rect res;
|
||||
res.x = 0; res.y = 0; res.w = 0; res.h = 0;
|
||||
return res;
|
||||
}
|
||||
|
||||
SDL_Rect dest;
|
||||
dest.x = x;
|
||||
dest.y = y;
|
||||
dest.w = surface->w;
|
||||
dest.h = surface->h;
|
||||
|
||||
if(dest.x + dest.w > area.x + area.w) {
|
||||
dest.w = area.x + area.w - dest.x;
|
||||
}
|
||||
|
||||
if(dest.y + dest.h > area.y + area.h) {
|
||||
dest.h = area.y + area.h - dest.y;
|
||||
}
|
||||
|
||||
if(gui != NULL) {
|
||||
SDL_Rect src = dest;
|
||||
src.x = 0;
|
||||
src.y = 0;
|
||||
SDL_BlitSurface(surface,&src,gui->video().getSurface(),&dest);
|
||||
}
|
||||
|
||||
SDL_FreeSurface(surface);
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
|
||||
SDL_Rect draw_text(display* gui, const SDL_Rect& area, int size,
|
||||
COLOUR colour, const std::string& txt, int x, int y,
|
||||
SDL_Surface* bg)
|
||||
{
|
||||
//make sure there's always at least a space, so we can ensure
|
||||
//that we can return a rectangle for height
|
||||
static const std::string blank_text(" ");
|
||||
const std::string& text = txt.empty() ? blank_text : txt;
|
||||
|
||||
SDL_Rect res;
|
||||
res.x = x;
|
||||
res.y = y;
|
||||
res.w = 0;
|
||||
res.h = 0;
|
||||
|
||||
std::string::const_iterator i1 = text.begin();
|
||||
std::string::const_iterator i2 = std::find(i1,text.end(),'\n');
|
||||
for(;;) {
|
||||
if(i1 != i2) {
|
||||
COLOUR col = NORMAL_COLOUR;
|
||||
int sz = size;
|
||||
if(*i1 == '#') {
|
||||
col = BAD_COLOUR;
|
||||
++i1;
|
||||
} else if(*i1 == '@') {
|
||||
col = GOOD_COLOUR;
|
||||
++i1;
|
||||
} else if(*i1 == '+') {
|
||||
sz += 2;
|
||||
++i1;
|
||||
} else if(*i1 == '-') {
|
||||
sz -= 2;
|
||||
++i1;
|
||||
}
|
||||
|
||||
if(i1 != i2) {
|
||||
const std::string new_string(i1,i2);
|
||||
const SDL_Rect rect =
|
||||
draw_text_line(gui,area,sz,col,new_string,x,y,bg);
|
||||
if(rect.w > res.w)
|
||||
res.w = rect.w;
|
||||
res.h += rect.h;
|
||||
y += rect.h;
|
||||
}
|
||||
}
|
||||
|
||||
if(i2 == text.end()) {
|
||||
break;
|
||||
}
|
||||
|
||||
i1 = i2+1;
|
||||
i2 = std::find(i1,text.end(),'\n');
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
37
src/font.hpp
Normal file
37
src/font.hpp
Normal file
|
@ -0,0 +1,37 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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 FONT_HPP_INCLUDED
|
||||
#define FONT_HPP_INCLUDED
|
||||
|
||||
#include "SDL.h"
|
||||
|
||||
#include "display.hpp"
|
||||
#include "video.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace font {
|
||||
|
||||
struct manager {
|
||||
manager();
|
||||
~manager();
|
||||
};
|
||||
|
||||
enum COLOUR { NORMAL_COLOUR, GOOD_COLOUR, BAD_COLOUR };
|
||||
|
||||
SDL_Rect draw_text(display* gui, const SDL_Rect& area, int size, COLOUR colour,
|
||||
const std::string& text, int x, int y, SDL_Surface* bg=NULL);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
407
src/game.cpp
Normal file
407
src/game.cpp
Normal file
|
@ -0,0 +1,407 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "SDL.h"
|
||||
|
||||
#include "actions.hpp"
|
||||
#include "ai.hpp"
|
||||
#include "config.hpp"
|
||||
#include "dialogs.hpp"
|
||||
#include "display.hpp"
|
||||
#include "font.hpp"
|
||||
#include "game_config.hpp"
|
||||
#include "game_events.hpp"
|
||||
#include "gamestatus.hpp"
|
||||
#include "key.hpp"
|
||||
#include "language.hpp"
|
||||
#include "menu.hpp"
|
||||
#include "multiplayer.hpp"
|
||||
#include "pathfind.hpp"
|
||||
#include "playlevel.hpp"
|
||||
#include "preferences.hpp"
|
||||
#include "replay.hpp"
|
||||
#include "sound.hpp"
|
||||
#include "team.hpp"
|
||||
#include "unit_types.hpp"
|
||||
#include "unit.hpp"
|
||||
#include "video.hpp"
|
||||
#include "widgets/button.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
LEVEL_RESULT play_game(display& disp, game_state& state, config& game_config,
|
||||
game_data& units_data, CVideo& video)
|
||||
{
|
||||
std::string type = state.campaign_type;
|
||||
if(type.empty())
|
||||
type = "scenario";
|
||||
const std::vector<config*>& scenarios = game_config.children[type];
|
||||
|
||||
for(int i = state.scenario; i < scenarios.size(); ++i) {
|
||||
std::vector<config*>& story = scenarios[i]->children["story"];
|
||||
|
||||
try {
|
||||
LEVEL_RESULT res = REPLAY;
|
||||
|
||||
state.label = scenarios[i]->values["name"];
|
||||
|
||||
recorder.set_save_info(state);
|
||||
|
||||
while(res == REPLAY) {
|
||||
state = recorder.get_save_info();
|
||||
if(!recorder.empty()) {
|
||||
recorder.start_replay();
|
||||
}
|
||||
|
||||
res = play_level(units_data,game_config,scenarios[i],
|
||||
video,state,story);
|
||||
}
|
||||
|
||||
recorder.clear();
|
||||
state.replay_data.clear();
|
||||
|
||||
if(res != VICTORY) {
|
||||
return res;
|
||||
}
|
||||
} catch(gamestatus::load_game_failed& e) {
|
||||
std::cerr << "The game could not be loaded: " << e.message << "\n";
|
||||
return QUIT;
|
||||
} catch(gamestatus::game_error& e) {
|
||||
std::cerr << "An error occurred while playing the game: "
|
||||
<< e.message << "\n";
|
||||
return QUIT;
|
||||
} catch(gamemap::incorrect_format_exception& e) {
|
||||
std::cerr << "The game map could not be loaded: " << e.msg_ << "\n";
|
||||
return QUIT;
|
||||
}
|
||||
|
||||
//skip over any scenarios which should be skipped, because their
|
||||
//conditions aren't met
|
||||
while(i+1 < scenarios.size()) {
|
||||
bool skip = false;
|
||||
|
||||
std::vector<config*>& conditions =
|
||||
scenarios[i+1]->children["condition"];
|
||||
for(std::vector<config*>::iterator cond = conditions.begin();
|
||||
cond != conditions.end(); ++cond) {
|
||||
if(game_events::conditional_passed(state,NULL,**cond) == false)
|
||||
skip = true;
|
||||
}
|
||||
|
||||
if(!skip)
|
||||
break;
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
//if this isn't the last scenario, then save the game
|
||||
if(i+1 < scenarios.size()) {
|
||||
state.label = scenarios[i+1]->values["name"];
|
||||
state.scenario = i+1;
|
||||
|
||||
const int should_save = gui::show_dialog(disp,NULL,"",
|
||||
string_table["save_game_message"],
|
||||
gui::YES_NO,NULL,NULL,
|
||||
string_table["save_game_label"],&state.label);
|
||||
|
||||
if(should_save == 0) {
|
||||
save_game(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return VICTORY;
|
||||
}
|
||||
|
||||
int play_game(int argc, char** argv)
|
||||
{
|
||||
std::string text_chr = read_file("data/text.chr");
|
||||
text_chr.resize(256*8);
|
||||
|
||||
CVideo video(text_chr.c_str());
|
||||
const font::manager font_manager;
|
||||
const sound::manager sound_manager;
|
||||
const preferences::manager prefs_manager;
|
||||
|
||||
std::map<std::string,std::string> defines_map;
|
||||
defines_map["NORMAL"] = "";
|
||||
std::vector<line_source> line_src;
|
||||
|
||||
std::string game_cfg = preprocess_file("data/game.cfg",&defines_map,
|
||||
&line_src);
|
||||
|
||||
config game_config(game_cfg,&line_src);
|
||||
|
||||
//clear game_cfg so it doesn't take up memory
|
||||
std::string().swap(game_cfg);
|
||||
|
||||
const std::vector<config*>& units = game_config.children["units"];
|
||||
if(units.empty()) {
|
||||
std::cerr << "Could not find units configuration\n";
|
||||
std::cerr << game_config.write();
|
||||
return 0;
|
||||
}
|
||||
|
||||
game_data units_data(*units[0]);
|
||||
|
||||
const bool lang_res = set_language(get_locale(), game_config);
|
||||
if(!lang_res) {
|
||||
std::cerr << "No translation for locale '" << get_locale()
|
||||
<< "', default to locale 'en'\n";
|
||||
|
||||
const bool lang_res = set_language("en", game_config);
|
||||
if(!lang_res) {
|
||||
std::cerr << "Language data not found\n";
|
||||
}
|
||||
}
|
||||
|
||||
bool test_mode = false;
|
||||
int video_flags = preferences::fullscreen() ? FULL_SCREEN : 0;
|
||||
|
||||
for(int arg = 1; arg != argc; ++arg) {
|
||||
const std::string val(argv[arg]);
|
||||
if(val == "-windowed") {
|
||||
video_flags = 0;
|
||||
} else if(val == "-test") {
|
||||
test_mode = true;
|
||||
} else if(val == "-debug") {
|
||||
game_config::debug = true;
|
||||
}
|
||||
}
|
||||
|
||||
const std::pair<int,int>& resolution = preferences::resolution();
|
||||
|
||||
std::cerr << "checking mode possible...\n";
|
||||
const int bpp = video.modePossible(resolution.first,resolution.second,
|
||||
16,video_flags);
|
||||
|
||||
std::cerr << bpp << "\n";
|
||||
|
||||
if(bpp == 0) {
|
||||
std::cerr << "The required video mode, " << resolution.first
|
||||
<< "x" << resolution.second << "x16 "
|
||||
<< "is not supported\n";
|
||||
|
||||
if(video_flags == FULL_SCREEN && argc == 0)
|
||||
std::cerr << "Try running the program with the -windowed option "
|
||||
<< "using a 16bpp X windows setting\n";
|
||||
|
||||
if(video_flags == 0 && argc == 0)
|
||||
std::cerr << "Try running with the -fullscreen option\n";
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(bpp != 16) {
|
||||
std::cerr << "Video mode must be emulated; the game may run slowly. "
|
||||
<< "For best results, run the program on a 16 bpp display\n";
|
||||
}
|
||||
|
||||
std::cerr << "setting mode to " << resolution.first << "x" << resolution.second << "\n";
|
||||
const int res = video.setMode(resolution.first,resolution.second,
|
||||
16,video_flags);
|
||||
if(res != 16) {
|
||||
std::cerr << "required video mode, " << resolution.first << "x"
|
||||
<< resolution.second << "x16 is not supported\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
SDL_WM_SetCaption(string_table["game_title"].c_str(), NULL);
|
||||
|
||||
for(;;) {
|
||||
sound::play_music("wesnoth-1.ogg");
|
||||
|
||||
game_state state;
|
||||
|
||||
display::unit_map u_map;
|
||||
config dummy_cfg("");
|
||||
display disp(u_map,video,gamemap(dummy_cfg,"1"),gamestatus(0),
|
||||
std::vector<team>());
|
||||
|
||||
if(test_mode) {
|
||||
state.campaign_type = "test";
|
||||
state.scenario = 0;
|
||||
|
||||
play_game(disp,state,game_config,units_data,video);
|
||||
return 0;
|
||||
}
|
||||
|
||||
gui::TITLE_RESULT res = gui::show_title(disp);
|
||||
if(res == gui::QUIT_GAME) {
|
||||
return 0;
|
||||
} else if(res == gui::LOAD_GAME) {
|
||||
srand(SDL_GetTicks());
|
||||
|
||||
const std::vector<std::string>& games = get_saves_list();
|
||||
|
||||
if(games.empty()) {
|
||||
gui::show_dialog(disp,NULL,
|
||||
string_table["no_saves_heading"],
|
||||
string_table["no_saves_message"],
|
||||
gui::OK_ONLY);
|
||||
continue;
|
||||
}
|
||||
|
||||
const int res = gui::show_dialog(disp,NULL,
|
||||
string_table["load_game_heading"],
|
||||
string_table["load_game_message"],
|
||||
gui::OK_CANCEL, &games);
|
||||
if(res == -1)
|
||||
continue;
|
||||
|
||||
try {
|
||||
load_game(units_data,games[res],state);
|
||||
defines_map.clear();
|
||||
defines_map[state.difficulty] = "";
|
||||
} catch(gamestatus::load_game_failed& e) {
|
||||
gui::show_dialog(disp,NULL,string_table["bad_save_heading"],
|
||||
string_table["bad_save_message"],gui::OK_ONLY);
|
||||
continue;
|
||||
}
|
||||
|
||||
recorder = replay(state.replay_data);
|
||||
if(!recorder.empty()) {
|
||||
const int res = gui::show_dialog(disp,NULL,
|
||||
"", string_table["replay_game_message"],
|
||||
gui::YES_NO);
|
||||
//if yes, then show the replay, otherwise
|
||||
//skip showing the replay
|
||||
if(res == 0) {
|
||||
recorder.set_skip(0);
|
||||
} else {
|
||||
std::cerr << "skipping...\n";
|
||||
recorder.set_skip(-1);
|
||||
}
|
||||
}
|
||||
|
||||
if(state.campaign_type == "multiplayer") {
|
||||
recorder.set_save_info(state);
|
||||
std::vector<config*> story;
|
||||
|
||||
try {
|
||||
play_level(units_data,game_config,&state.starting_pos,
|
||||
video,state,story);
|
||||
recorder.clear();
|
||||
} catch(gamestatus::load_game_failed& e) {
|
||||
std::cerr << "error loading the game: " << e.message
|
||||
<< "\n";
|
||||
return 0;
|
||||
} catch(gamestatus::game_error& e) {
|
||||
std::cerr << "error while playing the game: "
|
||||
<< e.message << "\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
} else if(res == gui::TUTORIAL) {
|
||||
state.campaign_type = "tutorial";
|
||||
state.scenario = 0;
|
||||
} else if(res == gui::NEW_CAMPAIGN) {
|
||||
state.campaign_type = "scenario";
|
||||
state.scenario = 0;
|
||||
|
||||
static const std::string difficulties[] = {"EASY","NORMAL","HARD"};
|
||||
const int ndiff = sizeof(difficulties)/sizeof(*difficulties);
|
||||
std::vector<std::string> options;
|
||||
|
||||
for(int i = 0; i != ndiff; ++i) {
|
||||
options.push_back(string_table[difficulties[i]]);
|
||||
if(options.back().empty())
|
||||
options.back() = difficulties[i];
|
||||
}
|
||||
|
||||
const int res = gui::show_dialog(disp,NULL,"",
|
||||
string_table["difficulty_level"],
|
||||
gui::OK_CANCEL,&options);
|
||||
if(res == -1)
|
||||
continue;
|
||||
|
||||
assert(res >= 0 && res < options.size());
|
||||
|
||||
state.difficulty = difficulties[res];
|
||||
defines_map.clear();
|
||||
defines_map[difficulties[res]] = "";
|
||||
} else if(res == gui::MULTIPLAYER) {
|
||||
state.campaign_type = "multiplayer";
|
||||
state.scenario = 0;
|
||||
|
||||
try {
|
||||
play_multiplayer(disp,units_data,game_config,state);
|
||||
} catch(gamestatus::load_game_failed& e) {
|
||||
std::cerr << "error loading the game: " << e.message
|
||||
<< "\n";
|
||||
return 0;
|
||||
} catch(gamestatus::game_error& e) {
|
||||
std::cerr << "error while playing the game: "
|
||||
<< e.message << "\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
continue;
|
||||
} else if(res == gui::CHANGE_LANGUAGE) {
|
||||
|
||||
const std::vector<std::string>& langs = get_languages(game_config);
|
||||
const int res = gui::show_dialog(disp,NULL,"",
|
||||
string_table["language_button"] + ":",
|
||||
gui::OK_CANCEL,&langs);
|
||||
if(res >= 0 && res < langs.size()) {
|
||||
set_language(langs[res], game_config);
|
||||
preferences::set_locale(langs[res]);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
//make a new game config item based on the difficulty level
|
||||
config game_config(preprocess_file("data/game.cfg", &defines_map));
|
||||
|
||||
const std::vector<config*>& units = game_config.children["units"];
|
||||
if(units.empty()) {
|
||||
std::cerr << "Could not find units configuration\n";
|
||||
std::cerr << game_config.write();
|
||||
return 0;
|
||||
}
|
||||
|
||||
game_data units_data(*units[0]);
|
||||
|
||||
const LEVEL_RESULT result = play_game(disp,state,game_config,
|
||||
units_data,video);
|
||||
if(result == VICTORY) {
|
||||
gui::show_dialog(disp,NULL,
|
||||
string_table["end_game_heading"],
|
||||
string_table["end_game_message"],
|
||||
gui::OK_ONLY);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
try {
|
||||
return play_game(argc,argv);
|
||||
} catch(CVideo::error&) {
|
||||
std::cerr << "Could not initialize video. Exiting.\n";
|
||||
} catch(config::error& e) {
|
||||
std::cerr << e.message << "\n";
|
||||
} catch(gui::button::error&) {
|
||||
std::cerr << "Could not create button: Image could not be found\n";
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
27
src/game_config.cpp
Normal file
27
src/game_config.cpp
Normal file
|
@ -0,0 +1,27 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "game_config.hpp"
|
||||
|
||||
namespace game_config
|
||||
{
|
||||
const int unit_cost = 1;
|
||||
const int base_income = 2;
|
||||
const int tower_income = 2;
|
||||
const int heal_amount = 4;
|
||||
const int healer_heals_per_turn = 8;
|
||||
const int cure_amount = 8;
|
||||
const int curer_heals_per_turn = 18;
|
||||
const int recall_cost = 20;
|
||||
const std::string version = "0.4.4";
|
||||
bool debug = false;
|
||||
}
|
33
src/game_config.hpp
Normal file
33
src/game_config.hpp
Normal file
|
@ -0,0 +1,33 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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 GAME_CONFIG_H_INCLUDED
|
||||
#define GAME_CONFIG_H_INCLUDED
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace game_config
|
||||
{
|
||||
extern const int unit_cost;
|
||||
extern const int base_income;
|
||||
extern const int tower_income;
|
||||
extern const int heal_amount;
|
||||
extern const int healer_heals_per_turn;
|
||||
extern const int cure_amount;
|
||||
extern const int curer_heals_per_turn;
|
||||
extern const int recall_cost;
|
||||
extern const std::string version;
|
||||
|
||||
extern bool debug;
|
||||
}
|
||||
|
||||
#endif
|
740
src/game_events.cpp
Normal file
740
src/game_events.cpp
Normal file
|
@ -0,0 +1,740 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "game_events.hpp"
|
||||
#include "menu.hpp"
|
||||
#include "language.hpp"
|
||||
#include "playlevel.hpp"
|
||||
#include "replay.hpp"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <deque>
|
||||
#include <iostream>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
namespace game_events {
|
||||
|
||||
bool conditional_passed(game_state& state_of_game,
|
||||
const std::map<gamemap::location,unit>* units,
|
||||
config& cond)
|
||||
{
|
||||
//if the if statement requires we have a certain unit, then
|
||||
//check for that.
|
||||
std::vector<config*>& have_unit = cond.children["have_unit"];
|
||||
|
||||
for(std::vector<config*>::iterator u = have_unit.begin();
|
||||
u != have_unit.end(); ++u) {
|
||||
|
||||
if(units == NULL)
|
||||
return false;
|
||||
|
||||
std::map<gamemap::location,unit>::const_iterator itor;
|
||||
for(itor = units->begin(); itor != units->end(); ++itor) {
|
||||
if(itor->second.matches_filter(**u)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(itor == units->end()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//check against each variable statement to see if the variable
|
||||
//matches the conditions or not
|
||||
std::vector<config*>& variables = cond.children["variable"];
|
||||
for(std::vector<config*>::iterator var = variables.begin();
|
||||
var != variables.end(); ++var) {
|
||||
std::map<std::string,std::string>& values = (*var)->values;
|
||||
std::map<std::string,std::string>& vars = state_of_game.variables;
|
||||
const std::string& name = values["name"];
|
||||
|
||||
//if we don't have a record of the variable, then the statement
|
||||
//is not true, unless it's a not equals statement, in which it's
|
||||
//automatically true
|
||||
if(vars.find(name) == vars.end()) {
|
||||
if(values.find("not_equals") == values.end() &&
|
||||
values.find("numerical_not_equals") == values.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string& value = vars[name];
|
||||
const double num_value = atof(value.c_str());
|
||||
|
||||
std::map<std::string,std::string>::iterator itor;
|
||||
|
||||
itor = values.find("equals");
|
||||
if(itor != values.end() && itor->second != value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
itor = values.find("numerical_equals");
|
||||
if(itor != values.end() && atof(itor->second.c_str()) != num_value){
|
||||
return false;
|
||||
}
|
||||
|
||||
itor = values.find("not_equals");
|
||||
if(itor != values.end() && itor->second == value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
itor = values.find("numerical_not_equals");
|
||||
if(itor != values.end() && atof(itor->second.c_str()) == num_value){
|
||||
return false;
|
||||
}
|
||||
|
||||
itor = values.find("greater_than");
|
||||
if(itor != values.end() && atof(itor->second.c_str()) >= num_value){
|
||||
return false;
|
||||
}
|
||||
|
||||
itor = values.find("less_than");
|
||||
if(itor != values.end() && atof(itor->second.c_str()) <= num_value){
|
||||
return false;
|
||||
}
|
||||
|
||||
itor = values.find("greater_than_equal_to");
|
||||
if(itor != values.end() && atof(itor->second.c_str()) > num_value){
|
||||
return false;
|
||||
}
|
||||
|
||||
itor = values.find("less_than_equal_to");
|
||||
if(itor != values.end() && atof(itor->second.c_str()) < num_value){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} //end namespace game_events
|
||||
|
||||
namespace {
|
||||
|
||||
display* screen = NULL;
|
||||
gamemap* game_map = NULL;
|
||||
std::map<gamemap::location,unit>* units = NULL;
|
||||
game_state* state_of_game = NULL;
|
||||
game_data* game_data_ptr = NULL;
|
||||
|
||||
bool events_init() { return screen != NULL; }
|
||||
|
||||
struct queued_event {
|
||||
queued_event(const std::string& name, const gamemap::location& loc1,
|
||||
const gamemap::location& loc2)
|
||||
: name(name), loc1(loc1), loc2(loc2) {}
|
||||
|
||||
std::string name;
|
||||
gamemap::location loc1;
|
||||
gamemap::location loc2;
|
||||
};
|
||||
|
||||
std::deque<queued_event> events_queue;
|
||||
|
||||
class event_handler
|
||||
{
|
||||
public:
|
||||
event_handler(config* cfg) : name_(cfg->values["name"]),
|
||||
first_time_only_(cfg->values["first_time_only"] != "no"),
|
||||
disabled_(false),
|
||||
cfg_(cfg)
|
||||
{}
|
||||
|
||||
const std::string& name() const { return name_; }
|
||||
|
||||
bool first_time_only() const { return first_time_only_; }
|
||||
|
||||
void disable() { disabled_ = true; }
|
||||
bool disabled() const { return disabled_; }
|
||||
|
||||
std::vector<config*>& first_arg_filters() {
|
||||
return cfg_->children["filter"];
|
||||
}
|
||||
|
||||
std::vector<config*>& second_arg_filters() {
|
||||
return cfg_->children["filter_second"];
|
||||
}
|
||||
|
||||
void handle_event(const queued_event& event_info, config* cfg=NULL);
|
||||
|
||||
private:
|
||||
std::string name_;
|
||||
bool first_time_only_;
|
||||
bool disabled_;
|
||||
config* cfg_;
|
||||
};
|
||||
|
||||
std::multimap<std::string,event_handler> events_map;
|
||||
|
||||
void event_handler::handle_event(const queued_event& event_info, config* cfg)
|
||||
{
|
||||
if(cfg == NULL)
|
||||
cfg = cfg_;
|
||||
|
||||
//sub commands that need to be handled in a guaranteed ordering
|
||||
std::vector<config*>& commands = cfg->children["command"];
|
||||
for(std::vector<config*>::iterator cmd = commands.begin();
|
||||
cmd != commands.end(); ++cmd) {
|
||||
handle_event(event_info,*cmd);
|
||||
}
|
||||
|
||||
//setting a variable
|
||||
std::vector<config*>& set_vars = cfg->children["set_variable"];
|
||||
for(std::vector<config*>::iterator var = set_vars.begin();
|
||||
var != set_vars.end(); ++var) {
|
||||
std::map<std::string,std::string>& vals = (*var)->values;
|
||||
const std::string& name = vals["name"];
|
||||
const std::string& value = vals["value"];
|
||||
if(value.empty() == false) {
|
||||
state_of_game->variables[name] = value;
|
||||
}
|
||||
|
||||
const std::string& add = vals["add"];
|
||||
if(add.empty() == false) {
|
||||
double value = atof(state_of_game->variables[name].c_str());
|
||||
value += atof(add.c_str());
|
||||
char buf[50];
|
||||
sprintf(buf,"%f",value);
|
||||
state_of_game->variables[name] = buf;
|
||||
}
|
||||
|
||||
const std::string& multiply = vals["multiply"];
|
||||
if(multiply.empty() == false) {
|
||||
double value = atof(state_of_game->variables[name].c_str());
|
||||
value *= atof(multiply.c_str());
|
||||
char buf[50];
|
||||
sprintf(buf,"%f",value);
|
||||
state_of_game->variables[name] = buf;
|
||||
}
|
||||
}
|
||||
|
||||
//conditional statements
|
||||
std::vector<config*>& conditionals = cfg->children["if"];
|
||||
for(std::vector<config*>::iterator cond = conditionals.begin();
|
||||
cond != conditionals.end(); ++cond) {
|
||||
const std::string type = game_events::conditional_passed(
|
||||
*state_of_game,units,**cond) ? "then":"else";
|
||||
|
||||
//if the if statement passed, then execute all 'then' statements,
|
||||
//otherwise execute 'else' statements
|
||||
std::vector<config*>& commands = cfg->children[type];
|
||||
for(std::vector<config*>::iterator cmd = commands.begin();
|
||||
cmd != commands.end(); ++cmd) {
|
||||
handle_event(event_info,*cmd);
|
||||
}
|
||||
}
|
||||
|
||||
//if we are assigning a role to a unit from the available units list
|
||||
std::vector<config*>& assign_role = cfg->children["role"];
|
||||
for(std::vector<config*>::iterator rl = assign_role.begin();
|
||||
rl != assign_role.end(); ++rl) {
|
||||
|
||||
//get a list of the types this unit can be
|
||||
std::vector<std::string> types = config::split((*rl)->values["type"]);
|
||||
|
||||
//iterate over all the types, and for each type, try to find
|
||||
//a unit that matches
|
||||
std::vector<std::string>::iterator ti;
|
||||
for(ti = types.begin(); ti != types.end(); ++ti) {
|
||||
config cfg;
|
||||
cfg.values["type"] = *ti;
|
||||
|
||||
std::map<gamemap::location,unit>::iterator itor;
|
||||
for(itor = units->begin(); itor != units->end(); ++itor) {
|
||||
if(itor->second.matches_filter(cfg)) {
|
||||
itor->second.assign_role((*rl)->values["role"]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(itor != units->end())
|
||||
break;
|
||||
|
||||
std::vector<unit>::iterator ui;
|
||||
//iterate over the units, and try to find one that matches
|
||||
for(ui = state_of_game->available_units.begin();
|
||||
ui != state_of_game->available_units.end(); ++ui) {
|
||||
if(ui->matches_filter(cfg)) {
|
||||
ui->assign_role((*rl)->values["role"]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//if we found a unit, we don't have to keep going.
|
||||
if(ui != state_of_game->available_units.end())
|
||||
break;
|
||||
}
|
||||
|
||||
//if we found a unit in the current type, we don't have to
|
||||
//look through any more types
|
||||
if(ti != types.end())
|
||||
break;
|
||||
}
|
||||
|
||||
std::vector<config*>& remove_overlays = cfg->children["removeitem"];
|
||||
for(std::vector<config*>::iterator rm = remove_overlays.begin();
|
||||
rm != remove_overlays.end(); ++rm) {
|
||||
gamemap::location loc(**rm);
|
||||
if(!loc.valid()) {
|
||||
loc = event_info.loc1;
|
||||
}
|
||||
|
||||
screen->remove_overlay(loc);
|
||||
}
|
||||
|
||||
//adding new items
|
||||
std::vector<config*>& add_overlays = cfg->children["item"];
|
||||
for(std::vector<config*>::iterator ni = add_overlays.begin();
|
||||
ni != add_overlays.end(); ++ni) {
|
||||
gamemap::location loc(**ni);
|
||||
const std::string& img = (*ni)->values["image"];
|
||||
if(!img.empty())
|
||||
screen->add_overlay(loc,img);
|
||||
}
|
||||
|
||||
//changing the terrain
|
||||
std::vector<config*>& terrain_changes = cfg->children["terrain"];
|
||||
for(std::vector<config*>::iterator tc = terrain_changes.begin();
|
||||
tc != terrain_changes.end(); ++tc) {
|
||||
gamemap::location loc(**tc);
|
||||
const std::string& terrain_type = (*tc)->values["letter"];
|
||||
if(terrain_type.size() > 0) {
|
||||
game_map->set_terrain(loc,terrain_type[0]);
|
||||
screen->recalculate_minimap();
|
||||
screen->invalidate_all();
|
||||
}
|
||||
}
|
||||
|
||||
//if we should spawn a new unit on the map somewhere
|
||||
std::vector<config*>& new_units = cfg->children["unit"];
|
||||
for(std::vector<config*>::iterator ui = new_units.begin();
|
||||
ui != new_units.end(); ++ui) {
|
||||
unit new_unit(*game_data_ptr,**ui);
|
||||
gamemap::location loc(**ui);
|
||||
|
||||
if(game_map->on_board(loc)) {
|
||||
loc = find_vacant_tile(*game_map,*units,loc);
|
||||
units->insert(std::pair<gamemap::location,unit>(loc,new_unit));
|
||||
} else {
|
||||
state_of_game->available_units.push_back(new_unit);
|
||||
}
|
||||
}
|
||||
|
||||
//if we should recall units that match a certain description
|
||||
std::vector<config*>& recalls = cfg->children["recall"];
|
||||
for(std::vector<config*>::iterator ir = recalls.begin();
|
||||
ir != recalls.end(); ++ir) {
|
||||
std::vector<unit>& avail = state_of_game->available_units;
|
||||
for(std::vector<unit>::iterator u = avail.begin();
|
||||
u != avail.end(); ++u) {
|
||||
if(u->matches_filter(**ir)) {
|
||||
recruit_unit(*game_map,1,*units,*u,gamemap::location(),screen);
|
||||
u = avail.erase(u);
|
||||
if(u == avail.end())
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<config*>& objects = cfg->children["object"];
|
||||
for(std::vector<config*>::iterator obj = objects.begin();
|
||||
obj != objects.end(); ++obj) {
|
||||
std::map<std::string,std::string>& values = (*obj)->values;
|
||||
|
||||
//if this item has already been used
|
||||
if(values["used"].empty() == false)
|
||||
continue;
|
||||
|
||||
const std::string& id = values["id"];
|
||||
|
||||
const std::string image = values["image"];
|
||||
std::string caption = values["name"];
|
||||
|
||||
const std::string& caption_lang = string_table[id + "_name"];
|
||||
if(caption_lang.empty() == false)
|
||||
caption = caption_lang;
|
||||
|
||||
const std::map<gamemap::location,unit>::iterator u =
|
||||
units->find(event_info.loc1);
|
||||
|
||||
if(u == units->end())
|
||||
continue;
|
||||
|
||||
std::string text;
|
||||
|
||||
std::vector<config*>& filters = (*obj)->children["filter"];
|
||||
|
||||
if(filters.empty() || u->second.matches_filter(*filters[0])) {
|
||||
const std::string& lang = string_table[id];
|
||||
if(!lang.empty())
|
||||
text = lang;
|
||||
else
|
||||
text = values["description"];
|
||||
|
||||
u->second.add_modification("object",**obj);
|
||||
screen->remove_overlay(event_info.loc1);
|
||||
screen->select_hex(event_info.loc1);
|
||||
screen->invalidate_unit();
|
||||
|
||||
//mark that this item won't be used again
|
||||
values["used"] = "true";
|
||||
} else {
|
||||
const std::string& lang = string_table[id + "_cannot_use"];
|
||||
if(!lang.empty())
|
||||
text = lang;
|
||||
else
|
||||
text = values["cannot_use_message"];
|
||||
}
|
||||
|
||||
|
||||
SDL_Surface* surface = NULL;
|
||||
if(image.empty() == false) {
|
||||
surface = screen->getImage(image,display::UNSCALED);
|
||||
}
|
||||
|
||||
gui::show_dialog(*screen,surface,caption,text);
|
||||
|
||||
//this will redraw the unit, with its new stats
|
||||
screen->draw();
|
||||
}
|
||||
|
||||
std::vector<config*>& messages = cfg->children["message"];
|
||||
for(std::vector<config*>::iterator msg = messages.begin();
|
||||
msg != messages.end(); ++msg) {
|
||||
std::map<std::string,std::string>& values = (*msg)->values;
|
||||
std::map<gamemap::location,unit>::iterator speaker = units->end();
|
||||
if(values["speaker"] == "unit") {
|
||||
speaker = units->find(event_info.loc1);
|
||||
} else if(values["speaker"] == "second_unit") {
|
||||
speaker = units->find(event_info.loc2);
|
||||
} else if(values["speaker"] != "narrator") {
|
||||
for(speaker = units->begin(); speaker != units->end();
|
||||
++speaker){
|
||||
if(speaker->second.matches_filter(**msg))
|
||||
break;
|
||||
}
|
||||
|
||||
if(speaker == units->end()) {
|
||||
//no matching unit found, so the dialog can't come up
|
||||
//continue onto the next message
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& id = values["id"];
|
||||
|
||||
std::string image = (*msg)->values["image"];
|
||||
std::string caption;
|
||||
|
||||
const std::string& lang_caption = string_table[id + "_caption"];
|
||||
if(!lang_caption.empty())
|
||||
caption = lang_caption;
|
||||
else
|
||||
caption = (*msg)->values["caption"];
|
||||
|
||||
if(speaker != units->end()) {
|
||||
screen->highlight_hex(speaker->first);
|
||||
screen->scroll_to_tile(speaker->first.x,speaker->first.y);
|
||||
|
||||
if(image.empty()) {
|
||||
image = speaker->second.type().image_profile();
|
||||
}
|
||||
|
||||
if(caption.empty()) {
|
||||
caption = speaker->second.description();
|
||||
if(caption.empty()) {
|
||||
caption = speaker->second.type().language_name();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> options;
|
||||
std::vector<std::vector<config*>*> option_events;
|
||||
|
||||
std::vector<config*>& menu_items = (*msg)->children["option"];
|
||||
for(std::vector<config*>::iterator mi = menu_items.begin();
|
||||
mi != menu_items.end(); ++mi) {
|
||||
const std::string& lang_msg = string_table[(*mi)->values["id"]];
|
||||
const std::string& msg = lang_msg.empty() ?
|
||||
(*mi)->values["message"] : lang_msg;
|
||||
options.push_back(msg);
|
||||
option_events.push_back(&(*mi)->children["command"]);
|
||||
}
|
||||
|
||||
SDL_Surface* surface = NULL;
|
||||
if(image.empty() == false) {
|
||||
surface = screen->getImage(image,display::UNSCALED);
|
||||
}
|
||||
|
||||
const std::string& lang_message = string_table[id];
|
||||
int option_chosen = gui::show_dialog(*screen,surface,caption,
|
||||
lang_message.empty() ? values["message"] : lang_message,
|
||||
options.empty() ? gui::MESSAGE : gui::OK_ONLY,
|
||||
options.empty() ? NULL : &options);
|
||||
|
||||
if(screen->update_locked() && options.empty() == false) {
|
||||
config* const cfg = recorder.get_next_action();
|
||||
if(cfg == NULL || cfg->children["choose"].empty()) {
|
||||
std::cerr << "choice expected but none found\n";
|
||||
throw replay::error();
|
||||
}
|
||||
|
||||
const std::string& val =
|
||||
cfg->children["choose"].front()->values["value"];
|
||||
option_chosen = atol(val.c_str());
|
||||
|
||||
} else if(options.empty() == false) {
|
||||
recorder.choose_option(option_chosen);
|
||||
}
|
||||
|
||||
if(options.empty() == false) {
|
||||
assert(option_chosen >= 0 && option_chosen < menu_items.size());
|
||||
std::vector<config*>& events = *option_events[option_chosen];
|
||||
for(std::vector<config*>::iterator ev = events.begin();
|
||||
ev != events.end(); ++ev) {
|
||||
handle_event(event_info,*ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<config*>& dead_units = cfg->children["kill"];
|
||||
for(std::vector<config*>::iterator du = dead_units.begin();
|
||||
du != dead_units.end(); ++du) {
|
||||
|
||||
for(std::map<gamemap::location,unit>::iterator i = units->begin();
|
||||
i != units->end(); ++i) {
|
||||
while(i->second.matches_filter(**du) && i != units->end()) {
|
||||
units->erase(i);
|
||||
i = units->begin();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<unit>& avail_units = state_of_game->available_units;
|
||||
for(std::vector<unit>::iterator j = avail_units.begin();
|
||||
j != avail_units.end(); ++j) {
|
||||
while(j->matches_filter(**du) && j != avail_units.end()) {
|
||||
j = avail_units.erase(j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//adding of new events
|
||||
std::vector<config*>& new_events = cfg->children["event"];
|
||||
for(std::vector<config*>::iterator ne = new_events.begin();
|
||||
ne != new_events.end(); ++ne) {
|
||||
event_handler new_handler(*ne);
|
||||
events_map.insert(std::pair<std::string,event_handler>(
|
||||
new_handler.name(),new_handler));
|
||||
}
|
||||
|
||||
std::vector<config*>& end_level = cfg->children["endlevel"];
|
||||
if(end_level.empty() == false) {
|
||||
config* const end_info = end_level[0];
|
||||
const std::string& result = end_info->values["result"];
|
||||
if(result.size() == 0 || result == "victory") {
|
||||
const bool bonus = (end_info->values["bonus"] == "yes");
|
||||
throw end_level_exception(VICTORY,bonus);
|
||||
} else {
|
||||
throw end_level_exception(DEFEAT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool filter_loc_impl(const gamemap::location& loc, const std::string& xloc,
|
||||
const std::string& yloc)
|
||||
{
|
||||
if(std::find(xloc.begin(),xloc.end(),',') != xloc.end()) {
|
||||
std::vector<std::string> xlocs = config::split(xloc);
|
||||
std::vector<std::string> ylocs = config::split(yloc);
|
||||
|
||||
const int size = xlocs.size() < ylocs.size()?xlocs.size():ylocs.size();
|
||||
for(int i = 0; i != size; ++i) {
|
||||
if(filter_loc_impl(loc,xlocs[i],ylocs[i]))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!xloc.empty()) {
|
||||
const std::string::const_iterator dash =
|
||||
std::find(xloc.begin(),xloc.end(),'-');
|
||||
|
||||
if(dash != xloc.end()) {
|
||||
const std::string beg(xloc.begin(),dash);
|
||||
const std::string end(dash+1,xloc.end());
|
||||
|
||||
const int bot = atoi(beg.c_str()) - 1;
|
||||
const int top = atoi(end.c_str()) - 1;
|
||||
|
||||
if(loc.x < bot || loc.x > top)
|
||||
return false;
|
||||
} else {
|
||||
const int xval = atoi(xloc.c_str()) - 1;
|
||||
if(xval != loc.x)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(!yloc.empty()) {
|
||||
const std::string::const_iterator dash =
|
||||
std::find(yloc.begin(),yloc.end(),'-');
|
||||
|
||||
if(dash != yloc.end()) {
|
||||
const std::string beg(yloc.begin(),dash);
|
||||
const std::string end(dash+1,yloc.end());
|
||||
|
||||
const int bot = atoi(beg.c_str()) - 1;
|
||||
const int top = atoi(end.c_str()) - 1;
|
||||
|
||||
if(loc.y < bot || loc.y > top)
|
||||
return false;
|
||||
} else {
|
||||
const int yval = atoi(yloc.c_str()) - 1;
|
||||
if(yval != loc.y)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool filter_loc(const gamemap::location& loc, config& cfg)
|
||||
{
|
||||
const std::string& xloc = cfg.values["x"];
|
||||
const std::string& yloc = cfg.values["y"];
|
||||
|
||||
return filter_loc_impl(loc,xloc,yloc);
|
||||
}
|
||||
|
||||
bool process_event(event_handler& handler, const queued_event& ev)
|
||||
{
|
||||
if(handler.disabled())
|
||||
return false;
|
||||
|
||||
std::map<gamemap::location,unit>::iterator unit1 = units->find(ev.loc1);
|
||||
std::map<gamemap::location,unit>::iterator unit2 = units->find(ev.loc2);
|
||||
|
||||
std::vector<config*>& first_filters = handler.first_arg_filters();
|
||||
for(std::vector<config*>::iterator ffi = first_filters.begin();
|
||||
ffi != first_filters.end(); ++ffi) {
|
||||
if(!filter_loc(ev.loc1,**ffi))
|
||||
return false;
|
||||
|
||||
if(unit1 != units->end() && !unit1->second.matches_filter(**ffi)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<config*>& second_filters = handler.second_arg_filters();
|
||||
for(std::vector<config*>::iterator sfi = second_filters.begin();
|
||||
sfi != second_filters.end(); ++sfi) {
|
||||
if(!filter_loc(ev.loc2,**sfi))
|
||||
return false;
|
||||
|
||||
if(unit2 != units->end() && !unit2->second.matches_filter(**sfi)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//the event hasn't been filtered out, so execute the handler
|
||||
handler.handle_event(ev);
|
||||
|
||||
if(handler.first_time_only())
|
||||
handler.disable();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} //end anonymous namespace
|
||||
|
||||
namespace game_events {
|
||||
|
||||
manager::manager(config& cfg, display& gui_, gamemap& map_,
|
||||
std::map<gamemap::location,unit>& units_,
|
||||
game_state& state_of_game_, game_data& game_data_)
|
||||
{
|
||||
std::vector<config*>& events_list = cfg.children["event"];
|
||||
for(std::vector<config*>::iterator i = events_list.begin();
|
||||
i != events_list.end(); ++i) {
|
||||
event_handler new_handler(*i);
|
||||
events_map.insert(std::pair<std::string,event_handler>(
|
||||
new_handler.name(), new_handler));
|
||||
}
|
||||
|
||||
screen = &gui_;
|
||||
game_map = &map_;
|
||||
units = &units_;
|
||||
state_of_game = &state_of_game_;
|
||||
game_data_ptr = &game_data_;
|
||||
}
|
||||
|
||||
manager::~manager() {
|
||||
events_queue.clear();
|
||||
events_map.clear();
|
||||
screen = NULL;
|
||||
game_map = NULL;
|
||||
units = NULL;
|
||||
state_of_game = NULL;
|
||||
game_data_ptr = NULL;
|
||||
}
|
||||
|
||||
void raise(const std::string& event,
|
||||
const gamemap::location& loc1,
|
||||
const gamemap::location& loc2)
|
||||
{
|
||||
if(!events_init())
|
||||
return;
|
||||
|
||||
events_queue.push_back(queued_event(event,loc1,loc2));
|
||||
}
|
||||
|
||||
bool fire(const std::string& event,
|
||||
const gamemap::location& loc1,
|
||||
const gamemap::location& loc2)
|
||||
{
|
||||
raise(event,loc1,loc2);
|
||||
return pump();
|
||||
}
|
||||
|
||||
bool pump()
|
||||
{
|
||||
if(!events_init())
|
||||
return false;
|
||||
|
||||
bool result = false;
|
||||
|
||||
while(events_queue.empty() == false) {
|
||||
queued_event ev = events_queue.front();
|
||||
events_queue.pop_front(); //pop now for exception safety
|
||||
const std::string& event_name = ev.name;
|
||||
typedef std::multimap<std::string,event_handler>::iterator itor;
|
||||
|
||||
//find all handlers for this event in the map
|
||||
std::pair<itor,itor> i = events_map.equal_range(event_name);
|
||||
|
||||
while(i.first != i.second) {
|
||||
event_handler& handler = i.first->second;
|
||||
if(process_event(handler, ev))
|
||||
result = true;
|
||||
++i.first;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} //end namespace game_events
|
51
src/game_events.hpp
Normal file
51
src/game_events.hpp
Normal file
|
@ -0,0 +1,51 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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 GAME_EVENTS_H_INCLUDED
|
||||
#define GAME_EVENTS_H_INCLUDED
|
||||
|
||||
#include "config.hpp"
|
||||
#include "display.hpp"
|
||||
#include "gamestatus.hpp"
|
||||
#include "map.hpp"
|
||||
#include "unit.hpp"
|
||||
#include "unit_types.hpp"
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace game_events
|
||||
{
|
||||
|
||||
bool conditional_passed(game_state& state_of_game,
|
||||
const std::map<gamemap::location,unit>* units,
|
||||
config& cond);
|
||||
|
||||
struct manager {
|
||||
manager(config& cfg, display& disp, gamemap& map,
|
||||
std::map<gamemap::location,unit>& units,
|
||||
game_state& state_of_game, game_data& data);
|
||||
~manager();
|
||||
};
|
||||
|
||||
void raise(const std::string& event,
|
||||
const gamemap::location& loc1=gamemap::location::null_location,
|
||||
const gamemap::location& loc2=gamemap::location::null_location);
|
||||
|
||||
bool fire(const std::string& event,
|
||||
const gamemap::location& loc1=gamemap::location::null_location,
|
||||
const gamemap::location& loc2=gamemap::location::null_location);
|
||||
|
||||
bool pump();
|
||||
|
||||
}
|
||||
|
||||
#endif
|
146
src/gamestatus.cpp
Normal file
146
src/gamestatus.cpp
Normal file
|
@ -0,0 +1,146 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "filesystem.hpp"
|
||||
#include "gamestatus.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <sstream>
|
||||
|
||||
gamestatus::gamestatus(int num_turns) :
|
||||
timeofday_(DAWN),turn_(1),numTurns_(num_turns)
|
||||
{}
|
||||
|
||||
const std::string& gamestatus::timeofdayDescription(gamestatus::TIME t)
|
||||
{
|
||||
static const std::string times[] = { "Dawn", "Morning", "Afternoon",
|
||||
"Dusk", "FirstWatch", "SecondWatch" };
|
||||
return times[t];
|
||||
}
|
||||
|
||||
gamestatus::TIME gamestatus::timeofday() const
|
||||
{
|
||||
return timeofday_;
|
||||
}
|
||||
|
||||
int gamestatus::turn() const
|
||||
{
|
||||
return turn_;
|
||||
}
|
||||
|
||||
int gamestatus::number_of_turns() const
|
||||
{
|
||||
return numTurns_;
|
||||
}
|
||||
|
||||
bool gamestatus::next_turn()
|
||||
{
|
||||
timeofday_ = static_cast<TIME>(static_cast<int>(timeofday_)+1);
|
||||
if(timeofday_ == NUM_TIMES)
|
||||
timeofday_ = DAWN;
|
||||
++turn_;
|
||||
return turn_ <= numTurns_;
|
||||
}
|
||||
|
||||
game_state read_game(game_data& data, config* cfg)
|
||||
{
|
||||
game_state res;
|
||||
res.label = cfg->values["label"];
|
||||
res.gold = atoi(cfg->values["gold"].c_str());
|
||||
res.scenario = atoi(cfg->values["scenario"].c_str());
|
||||
|
||||
res.difficulty = cfg->values["difficulty"];
|
||||
if(res.difficulty.empty())
|
||||
res.difficulty = "NORMAL";
|
||||
|
||||
res.campaign_type = cfg->values["campaign_type"];
|
||||
if(res.campaign_type.empty())
|
||||
res.campaign_type = "scenario";
|
||||
|
||||
std::vector<config*>& units = cfg->children["unit"];
|
||||
for(std::vector<config*>::iterator i = units.begin();
|
||||
i != units.end(); ++i) {
|
||||
res.available_units.push_back(unit(data,**i));
|
||||
}
|
||||
|
||||
std::vector<config*>& vars = cfg->children["variables"];
|
||||
if(vars.empty() == false) {
|
||||
res.variables = vars[0]->values;
|
||||
}
|
||||
|
||||
std::vector<config*>& replays = cfg->children["replay"];
|
||||
if(replays.empty() == false) {
|
||||
res.replay_data = *replays[0];
|
||||
}
|
||||
|
||||
std::vector<config*>& starts = cfg->children["start"];
|
||||
if(starts.empty() == false) {
|
||||
res.starting_pos = *starts[0];
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void write_game(const game_state& game, config& cfg)
|
||||
{
|
||||
cfg.values["label"] = game.label;
|
||||
|
||||
char buf[50];
|
||||
sprintf(buf,"%d",game.gold);
|
||||
cfg.values["gold"] = buf;
|
||||
|
||||
sprintf(buf,"%d",game.scenario);
|
||||
cfg.values["scenario"] = buf;
|
||||
|
||||
cfg.values["campaign_type"] = game.campaign_type;
|
||||
|
||||
cfg.values["difficulty"] = game.difficulty;
|
||||
|
||||
config* vars = new config();
|
||||
vars->values = game.variables;
|
||||
cfg.children["variables"].push_back(vars);
|
||||
|
||||
for(std::vector<unit>::const_iterator i = game.available_units.begin();
|
||||
i != game.available_units.end(); ++i) {
|
||||
config* const new_cfg = new config;
|
||||
i->write(*new_cfg);
|
||||
cfg.children["unit"].push_back(new_cfg);
|
||||
}
|
||||
|
||||
if(game.replay_data.children.empty() == false) {
|
||||
cfg.children["replay"].push_back(new config(game.replay_data));
|
||||
}
|
||||
|
||||
cfg.children["start"].push_back(new config(game.starting_pos));
|
||||
}
|
||||
|
||||
std::vector<std::string> get_saves_list()
|
||||
{
|
||||
std::vector<std::string> res;
|
||||
get_files_in_dir(get_saves_dir(),&res);
|
||||
return res;
|
||||
}
|
||||
|
||||
void load_game(game_data& data, const std::string& name, game_state& state)
|
||||
{
|
||||
config cfg(read_file(get_saves_dir() + "/" + name));
|
||||
state = read_game(data,&cfg);
|
||||
}
|
||||
|
||||
void save_game(const game_state& state)
|
||||
{
|
||||
const std::string& name = state.label;
|
||||
|
||||
config cfg;
|
||||
write_game(state,cfg);
|
||||
write_file(get_saves_dir() + "/" + name,cfg.write());
|
||||
}
|
73
src/gamestatus.hpp
Normal file
73
src/gamestatus.hpp
Normal file
|
@ -0,0 +1,73 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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 GAME_STATUS_HPP_INCLUDED
|
||||
#define GAME_STATUS_HPP_INCLUDED
|
||||
|
||||
#include "unit.hpp"
|
||||
#include "unit_types.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class gamestatus
|
||||
{
|
||||
public:
|
||||
gamestatus(int num_turns);
|
||||
enum TIME { DAWN, DAY1, DAY2, DUSK, NIGHT1, NIGHT2, NUM_TIMES };
|
||||
|
||||
static const std::string& timeofdayDescription(TIME t);
|
||||
TIME timeofday() const;
|
||||
int turn() const;
|
||||
int number_of_turns() const;
|
||||
|
||||
bool next_turn();
|
||||
|
||||
struct load_game_failed {
|
||||
load_game_failed() {}
|
||||
load_game_failed(const std::string& msg) : message(msg) {}
|
||||
|
||||
std::string message;
|
||||
};
|
||||
|
||||
struct game_error {
|
||||
game_error(const std::string& msg) : message(msg) {}
|
||||
std::string message;
|
||||
};
|
||||
|
||||
private:
|
||||
TIME timeofday_;
|
||||
int turn_;
|
||||
int numTurns_;
|
||||
};
|
||||
|
||||
struct game_state
|
||||
{
|
||||
game_state() : gold(-1), difficulty("NORMAL") {}
|
||||
std::string label;
|
||||
std::string campaign_type;
|
||||
int scenario;
|
||||
int gold;
|
||||
std::vector<unit> available_units;
|
||||
std::map<std::string,std::string> variables;
|
||||
std::string difficulty;
|
||||
|
||||
config replay_data;
|
||||
config starting_pos;
|
||||
};
|
||||
|
||||
std::vector<std::string> get_saves_list();
|
||||
|
||||
void load_game(game_data& data, const std::string& name, game_state& state);
|
||||
void save_game(const game_state& state);
|
||||
|
||||
#endif
|
183
src/hotkeys.cpp
Normal file
183
src/hotkeys.cpp
Normal file
|
@ -0,0 +1,183 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "config.hpp"
|
||||
#include "hotkeys.hpp"
|
||||
#include "language.hpp"
|
||||
#include "menu.hpp"
|
||||
#include "playlevel.hpp"
|
||||
#include "preferences.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <map>
|
||||
|
||||
namespace {
|
||||
|
||||
HOTKEY_COMMAND string_to_command(const std::string& str)
|
||||
{
|
||||
static std::map<std::string,HOTKEY_COMMAND> m;
|
||||
if(m.empty()) {
|
||||
typedef std::pair<std::string,HOTKEY_COMMAND> val;
|
||||
m.insert(val("cycle",HOTKEY_CYCLE_UNITS));
|
||||
m.insert(val("endunitturn",HOTKEY_END_UNIT_TURN));
|
||||
m.insert(val("leader",HOTKEY_LEADER));
|
||||
m.insert(val("undo",HOTKEY_UNDO));
|
||||
m.insert(val("redo",HOTKEY_REDO));
|
||||
m.insert(val("zoomin",HOTKEY_ZOOM_IN));
|
||||
m.insert(val("zoomout",HOTKEY_ZOOM_OUT));
|
||||
m.insert(val("zoomdefault",HOTKEY_ZOOM_DEFAULT));
|
||||
m.insert(val("fullscreen",HOTKEY_FULLSCREEN));
|
||||
m.insert(val("accelerated",HOTKEY_ACCELERATED));
|
||||
m.insert(val("resistance",HOTKEY_ATTACK_RESISTANCE));
|
||||
m.insert(val("terraintable",HOTKEY_TERRAIN_TABLE));
|
||||
}
|
||||
|
||||
const std::map<std::string,HOTKEY_COMMAND>::const_iterator i = m.find(str);
|
||||
if(i == m.end())
|
||||
return HOTKEY_NULL;
|
||||
else
|
||||
return i->second;
|
||||
}
|
||||
|
||||
struct hotkey {
|
||||
explicit hotkey(config& cfg);
|
||||
|
||||
HOTKEY_COMMAND action;
|
||||
int keycode;
|
||||
bool alt, ctrl, shift;
|
||||
mutable bool lastres;
|
||||
};
|
||||
|
||||
hotkey::hotkey(config& cfg) : lastres(false)
|
||||
{
|
||||
std::map<std::string,std::string>& m = cfg.values;
|
||||
action = string_to_command(m["command"]);
|
||||
|
||||
keycode = m["key"].empty() ? 0 : m["key"][0];
|
||||
alt = (m["alt"] == "yes");
|
||||
ctrl = (m["ctrl"] == "yes");
|
||||
shift = (m["shift"] == "yes");
|
||||
}
|
||||
|
||||
bool operator==(const hotkey& a, const hotkey& b)
|
||||
{
|
||||
return a.keycode == b.keycode && a.alt == b.alt &&
|
||||
a.ctrl == b.ctrl && a.shift == b.shift;
|
||||
}
|
||||
|
||||
bool operator!=(const hotkey& a, const hotkey& b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
std::vector<hotkey> hotkeys;
|
||||
|
||||
}
|
||||
|
||||
struct hotkey_pressed {
|
||||
hotkey_pressed(CKey& key);
|
||||
|
||||
bool operator()(const hotkey& hk) const;
|
||||
|
||||
private:
|
||||
const bool shift_, ctrl_, alt_;
|
||||
CKey& key_;
|
||||
};
|
||||
|
||||
hotkey_pressed::hotkey_pressed(CKey& key) :
|
||||
shift_(key[SDLK_LSHIFT] || key[SDLK_RSHIFT]),
|
||||
ctrl_(key[SDLK_LCTRL] || key[SDLK_RCTRL]),
|
||||
alt_(key[SDLK_LALT] || key[SDLK_RALT]), key_(key) {}
|
||||
|
||||
bool hotkey_pressed::operator()(const hotkey& hk) const
|
||||
{
|
||||
const bool res = shift_ == hk.shift && ctrl_ == hk.ctrl &&
|
||||
alt_ == hk.alt && key_[hk.keycode];
|
||||
|
||||
//for zoom in and zoom out, allow it to happen multiple consecutive times
|
||||
if(hk.action == HOTKEY_ZOOM_IN || hk.action == HOTKEY_ZOOM_OUT) {
|
||||
hk.lastres = false;
|
||||
return res;
|
||||
}
|
||||
|
||||
//don't let it return true on multiple consecutive occurrences
|
||||
if(hk.lastres) {
|
||||
hk.lastres = res;
|
||||
return false;
|
||||
} else {
|
||||
hk.lastres = res;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
void add_hotkey(config& cfg)
|
||||
{
|
||||
const hotkey new_hotkey(cfg);
|
||||
const std::vector<hotkey>::iterator i =
|
||||
std::find(hotkeys.begin(),hotkeys.end(),new_hotkey);
|
||||
if(i != hotkeys.end()) {
|
||||
*i = new_hotkey;
|
||||
} else {
|
||||
hotkeys.push_back(new_hotkey);
|
||||
}
|
||||
}
|
||||
|
||||
void add_hotkeys(config& cfg)
|
||||
{
|
||||
std::vector<config*>& children = cfg.children["hotkey"];
|
||||
for(std::vector<config*>::iterator i = children.begin();
|
||||
i != children.end(); ++i) {
|
||||
add_hotkey(**i);
|
||||
}
|
||||
}
|
||||
|
||||
HOTKEY_COMMAND check_keys(display& disp)
|
||||
{
|
||||
const double zoom_amount = 5.0;
|
||||
|
||||
CKey key;
|
||||
if(key[KEY_ESCAPE]) {
|
||||
const int res = gui::show_dialog(disp,NULL,"",
|
||||
string_table["quit_message"],gui::YES_NO);
|
||||
if(res == 0) {
|
||||
throw end_level_exception(QUIT);
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<hotkey>::iterator i =
|
||||
std::find_if(hotkeys.begin(),hotkeys.end(),hotkey_pressed(key));
|
||||
if(i == hotkeys.end())
|
||||
return HOTKEY_NULL;
|
||||
|
||||
switch(i->action) {
|
||||
case HOTKEY_ZOOM_IN:
|
||||
disp.zoom(zoom_amount);
|
||||
break;
|
||||
case HOTKEY_ZOOM_OUT:
|
||||
disp.zoom(-zoom_amount);
|
||||
break;
|
||||
case HOTKEY_ZOOM_DEFAULT:
|
||||
disp.default_zoom();
|
||||
break;
|
||||
case HOTKEY_FULLSCREEN:
|
||||
preferences::set_fullscreen(!preferences::fullscreen());
|
||||
break;
|
||||
case HOTKEY_ACCELERATED:
|
||||
preferences::set_turbo(!preferences::turbo());
|
||||
break;
|
||||
default:
|
||||
return i->action;
|
||||
}
|
||||
|
||||
return HOTKEY_NULL;
|
||||
}
|
31
src/hotkeys.hpp
Normal file
31
src/hotkeys.hpp
Normal file
|
@ -0,0 +1,31 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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 HOTKEYS_HPP_INCLUDED
|
||||
#define HOTKEYS_HPP_INCLUDED
|
||||
|
||||
#include "config.hpp"
|
||||
#include "display.hpp"
|
||||
#include "key.hpp"
|
||||
|
||||
enum HOTKEY_COMMAND { HOTKEY_CYCLE_UNITS, HOTKEY_END_UNIT_TURN, HOTKEY_LEADER,
|
||||
HOTKEY_UNDO, HOTKEY_REDO,
|
||||
HOTKEY_ZOOM_IN, HOTKEY_ZOOM_OUT, HOTKEY_ZOOM_DEFAULT,
|
||||
HOTKEY_FULLSCREEN, HOTKEY_ACCELERATED,
|
||||
HOTKEY_TERRAIN_TABLE, HOTKEY_ATTACK_RESISTANCE,
|
||||
HOTKEY_NULL };
|
||||
|
||||
void add_hotkeys(config& cfg);
|
||||
|
||||
HOTKEY_COMMAND check_keys(display& disp);
|
||||
|
||||
#endif
|
260
src/intro.cpp
Normal file
260
src/intro.cpp
Normal file
|
@ -0,0 +1,260 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "font.hpp"
|
||||
#include "intro.hpp"
|
||||
#include "key.hpp"
|
||||
#include "language.hpp"
|
||||
#include "menu.hpp"
|
||||
#include "widgets/button.hpp"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
void show_intro(display& screen, config& data)
|
||||
{
|
||||
CKey key;
|
||||
|
||||
gui::button next_button(screen,string_table["next_button"] + ">>>");
|
||||
gui::button skip_button(screen,string_table["skip_button"]);
|
||||
|
||||
next_button.set_x(700);
|
||||
next_button.set_y(500);
|
||||
skip_button.set_x(700);
|
||||
skip_button.set_y(550);
|
||||
|
||||
std::vector<config*>& parts = data.children["part"];
|
||||
|
||||
for(std::vector<config*>::iterator i = parts.begin(); i != parts.end();++i){
|
||||
gui::draw_solid_tinted_rectangle(0,0,screen.x()-1,screen.y()-1,
|
||||
0,0,0,1.0,screen.video().getSurface());
|
||||
const std::string& image_name = (*i)->values["image"];
|
||||
SDL_Surface* image = NULL;
|
||||
if(image_name.empty() == false) {
|
||||
image = screen.getImage(image_name,display::UNSCALED);
|
||||
}
|
||||
|
||||
int textx = 200;
|
||||
int texty = 400;
|
||||
|
||||
if(image != NULL) {
|
||||
SDL_Rect dstrect;
|
||||
dstrect.x = screen.x()/2 - image->w/2;
|
||||
dstrect.y = screen.y()/2 - image->h/2;
|
||||
dstrect.w = image->w;
|
||||
dstrect.h = image->h;
|
||||
|
||||
SDL_BlitSurface(image,NULL,screen.video().getSurface(),&dstrect);
|
||||
|
||||
textx = dstrect.x;
|
||||
texty = dstrect.y + dstrect.h + 10;
|
||||
|
||||
next_button.set_x(dstrect.x+dstrect.w);
|
||||
next_button.set_y(dstrect.y+dstrect.h+20);
|
||||
skip_button.set_x(dstrect.x+dstrect.w);
|
||||
skip_button.set_y(dstrect.y+dstrect.h+70);
|
||||
}
|
||||
|
||||
const std::string& id = (*i)->values["id"];
|
||||
const std::string& lang_story = string_table[id];
|
||||
const std::string& story = lang_story.empty() ? (*i)->values["story"] :
|
||||
lang_story;
|
||||
|
||||
const int max_length = 60;
|
||||
std::stringstream stream;
|
||||
int cur_length = 0;
|
||||
|
||||
for(std::string::const_iterator j = story.begin(); j!=story.end();++j){
|
||||
char c = *j;
|
||||
if(c == ' ' && cur_length >= max_length)
|
||||
c = '\n';
|
||||
|
||||
if(c == '\n') {
|
||||
cur_length = 0;
|
||||
} else {
|
||||
++cur_length;
|
||||
}
|
||||
|
||||
stream << c;
|
||||
}
|
||||
|
||||
static const SDL_Rect area = {0,0,screen.x(),screen.y()};
|
||||
font::draw_text(&screen,area,16,font::NORMAL_COLOUR,stream.str(),
|
||||
textx,texty);
|
||||
next_button.draw();
|
||||
skip_button.draw();
|
||||
screen.video().update(0,0,screen.x(),screen.y());
|
||||
|
||||
const std::string& delay = (*i)->values["delay"];
|
||||
const int ndelay = atoi(delay.c_str());
|
||||
|
||||
bool last = true;
|
||||
for(int d = 0; d < ndelay*10; d += 50) {
|
||||
SDL_Delay(10);
|
||||
|
||||
int mousex, mousey;
|
||||
const int mouse_flags = SDL_GetMouseState(&mousex,&mousey);
|
||||
|
||||
const bool right_button = mouse_flags&SDL_BUTTON_RMASK;
|
||||
const bool left_button = mouse_flags&SDL_BUTTON_LMASK;
|
||||
|
||||
|
||||
if(key[KEY_ESCAPE] ||
|
||||
skip_button.process(mousex,mousey,left_button))
|
||||
return;
|
||||
|
||||
if(key[KEY_SPACE] || key[KEY_ENTER] ||
|
||||
next_button.process(mousex,mousey,left_button)) {
|
||||
if(!last)
|
||||
break;
|
||||
} else {
|
||||
last = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gui::draw_solid_tinted_rectangle(0,0,screen.x()-1,screen.y()-1,0,0,0,1.0,
|
||||
screen.video().getSurface());
|
||||
}
|
||||
|
||||
void show_map_scene(display& screen, config& data)
|
||||
{
|
||||
//clear the screen
|
||||
gui::draw_solid_tinted_rectangle(0,0,screen.x()-1,screen.y()-1,0,0,0,1.0,
|
||||
screen.video().getSurface());
|
||||
|
||||
std::vector<config*>& sequence = data.children["bigmap"];
|
||||
if(sequence.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
config& cfg = *sequence[0];
|
||||
|
||||
std::vector<config*>& dots = cfg.children["dot"];
|
||||
|
||||
const std::string& image_file = cfg.values["image"];
|
||||
|
||||
SDL_Surface* const image = screen.getImage(image_file,display::UNSCALED);
|
||||
SDL_Surface* const dot_image =
|
||||
screen.getImage("misc/dot.png",display::UNSCALED);
|
||||
SDL_Surface* const cross_image =
|
||||
screen.getImage("misc/cross.png",display::UNSCALED);
|
||||
if(image == NULL || dot_image == NULL || cross_image == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_Rect dstrect;
|
||||
dstrect.x = screen.x()/2 - image->w/2;
|
||||
dstrect.y = screen.y()/2 - image->h/2;
|
||||
dstrect.w = image->w;
|
||||
dstrect.h = image->h;
|
||||
|
||||
SDL_BlitSurface(image,NULL,screen.video().getSurface(),&dstrect);
|
||||
|
||||
const std::string& id = data.values["id"];
|
||||
const std::string& scenario_name = string_table[id];
|
||||
|
||||
const std::string& scenario = scenario_name.empty() ? data.values["name"] :
|
||||
scenario_name;
|
||||
screen.video().update(0,0,screen.x(),screen.y());
|
||||
|
||||
CKey key;
|
||||
|
||||
|
||||
for(std::vector<config*>::iterator d = dots.begin(); d != dots.end(); ++d){
|
||||
const std::string& xloc = (*d)->values["x"];
|
||||
const std::string& yloc = (*d)->values["y"];
|
||||
const int x = atoi(xloc.c_str());
|
||||
const int y = atoi(yloc.c_str());
|
||||
if(x < 0 || x >= image->w || y < 0 || y >= image->w)
|
||||
continue;
|
||||
|
||||
SDL_Surface* img = dot_image;
|
||||
if((*d)->values["type"] == "cross") {
|
||||
img = cross_image;
|
||||
}
|
||||
|
||||
int xdot = x - img->w/2;
|
||||
int ydot = y - img->h/2;
|
||||
|
||||
if(xdot < 0)
|
||||
xdot = 0;
|
||||
|
||||
if(ydot < 0)
|
||||
ydot = 0;
|
||||
|
||||
SDL_Rect dot_rect;
|
||||
dot_rect.x = xdot;
|
||||
dot_rect.y = ydot;
|
||||
dot_rect.w = img->w;
|
||||
dot_rect.h = img->h;
|
||||
|
||||
SDL_BlitSurface(img,NULL,image,&dot_rect);
|
||||
|
||||
SDL_BlitSurface(image,NULL,screen.video().getSurface(),&dstrect);
|
||||
|
||||
for(int i = 0; i != 10; ++i) {
|
||||
if(key[KEY_ESCAPE]) {
|
||||
break;
|
||||
}
|
||||
|
||||
SDL_Delay(50);
|
||||
|
||||
int a, b;
|
||||
const int mouse_flags = SDL_GetMouseState(&a,&b);
|
||||
if(key[KEY_ENTER] || key[KEY_SPACE] || mouse_flags) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(key[KEY_ESCAPE]) {
|
||||
break;
|
||||
}
|
||||
|
||||
screen.video().update(0,0,screen.x(),screen.y());
|
||||
}
|
||||
|
||||
if(!key[KEY_ESCAPE]) {
|
||||
SDL_Delay(500);
|
||||
}
|
||||
|
||||
static const SDL_Rect area = {0,0,screen.x(),screen.y()};
|
||||
const SDL_Rect scenario_size =
|
||||
font::draw_text(NULL,area,24,font::NORMAL_COLOUR,scenario,0,0);
|
||||
font::draw_text(&screen,area,24,font::NORMAL_COLOUR,scenario,
|
||||
dstrect.x,dstrect.y - scenario_size.h - 4);
|
||||
|
||||
SDL_BlitSurface(image,NULL,screen.video().getSurface(),&dstrect);
|
||||
screen.video().update(0,0,screen.x(),screen.y());
|
||||
|
||||
bool last_state = true;
|
||||
for(;;) {
|
||||
int a, b;
|
||||
const int mouse_flags = SDL_GetMouseState(&a,&b);
|
||||
|
||||
const bool new_state = mouse_flags || key[KEY_ESCAPE] ||
|
||||
key[KEY_ENTER] || key[KEY_SPACE];
|
||||
|
||||
if(new_state && !last_state)
|
||||
break;
|
||||
|
||||
last_state = new_state;
|
||||
|
||||
SDL_Delay(20);
|
||||
SDL_PumpEvents();
|
||||
}
|
||||
|
||||
//clear the screen
|
||||
gui::draw_solid_tinted_rectangle(0,0,screen.x()-1,screen.y()-1,0,0,0,1.0,
|
||||
screen.video().getSurface());
|
||||
}
|
26
src/intro.hpp
Normal file
26
src/intro.hpp
Normal file
|
@ -0,0 +1,26 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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 INTRO_HPP_INCLUDED
|
||||
#define INTRO_HPP_INCLUDED
|
||||
|
||||
#include "SDL.h"
|
||||
#include "config.hpp"
|
||||
#include "display.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
void show_intro(display& screen, config& data);
|
||||
|
||||
void show_map_scene(display& screen, config& data);
|
||||
|
||||
#endif
|
52
src/key.cpp
Normal file
52
src/key.cpp
Normal file
|
@ -0,0 +1,52 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "key.hpp"
|
||||
|
||||
#define KEY_TEST 0
|
||||
|
||||
#if (KEY_TEST == 1)
|
||||
|
||||
#include "video.hpp"
|
||||
|
||||
int main( void )
|
||||
{
|
||||
SDL_Init(SDL_INIT_VIDEO);
|
||||
CVideo video( 640, 480, 16, 0 );
|
||||
CKey key;
|
||||
printf( "press enter (escape exits)...\n" );
|
||||
for(;;) {
|
||||
if( key[KEY_RETURN] != 0 )
|
||||
printf( "key(ENTER) pressed\n" );
|
||||
if( key[KEY_ESCAPE] != 0 )
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
CKey::CKey() : is_enabled(true)
|
||||
{
|
||||
static int num_keys = 300;
|
||||
key_list = SDL_GetKeyState( &num_keys );
|
||||
}
|
||||
|
||||
int CKey::operator[]( int code )
|
||||
{
|
||||
SDL_PumpEvents();
|
||||
return (code == KEY_ESCAPE || is_enabled) && int(key_list[code]);
|
||||
}
|
||||
|
||||
void CKey::SetEnabled( bool enable )
|
||||
{
|
||||
is_enabled = enable;
|
||||
}
|
96
src/key.hpp
Normal file
96
src/key.hpp
Normal file
|
@ -0,0 +1,96 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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 KEY_HPP_INCLUDED
|
||||
#define KEY_HPP_INCLUDED
|
||||
|
||||
#include "SDL.h"
|
||||
|
||||
class CKey {
|
||||
public:
|
||||
CKey();
|
||||
|
||||
int operator[](int);
|
||||
void SetEnabled(bool enable);
|
||||
private:
|
||||
Uint8 *key_list;
|
||||
bool is_enabled;
|
||||
};
|
||||
|
||||
#define KEY_PAGEUP SDLK_PAGEUP
|
||||
#define KEY_PAGEDOWN SDLK_PAGEDOWN
|
||||
#define KEY_BACKSPACE SDLK_BACKSPACE
|
||||
#define KEY_DELETE SDLK_DELETE
|
||||
#define KEY_TAB SDLK_TAB
|
||||
#define KEY_CLEAR SDLK_CLEAR
|
||||
#define KEY_RETURN SDLK_RETURN
|
||||
#define KEY_ENTER KEY_RETURN
|
||||
#define KEY_ESCAPE SDLK_ESCAPE
|
||||
#define KEY_SPACE SDLK_SPACE
|
||||
#define KEY_0 SDLK_0
|
||||
#define KEY_1 SDLK_1
|
||||
#define KEY_2 SDLK_2
|
||||
#define KEY_3 SDLK_3
|
||||
#define KEY_4 SDLK_4
|
||||
#define KEY_5 SDLK_5
|
||||
#define KEY_6 SDLK_6
|
||||
#define KEY_7 SDLK_7
|
||||
#define KEY_8 SDLK_8
|
||||
#define KEY_9 SDLK_9
|
||||
#define KEY_KP0 SDLK_KP0
|
||||
#define KEY_KP1 SDLK_KP1
|
||||
#define KEY_KP2 SDLK_KP2
|
||||
#define KEY_KP3 SDLK_KP3
|
||||
#define KEY_KP4 SDLK_KP4
|
||||
#define KEY_KP5 SDLK_KP5
|
||||
#define KEY_KP6 SDLK_KP6
|
||||
#define KEY_KP7 SDLK_KP7
|
||||
#define KEY_KP8 SDLK_KP8
|
||||
#define KEY_KP9 SDLK_KP9
|
||||
#define KEY_A SDLK_a
|
||||
#define KEY_B SDLK_b
|
||||
#define KEY_C SDLK_c
|
||||
#define KEY_D SDLK_d
|
||||
#define KEY_E SDLK_e
|
||||
#define KEY_F SDLK_f
|
||||
#define KEY_G SDLK_g
|
||||
#define KEY_H SDLK_h
|
||||
#define KEY_I SDLK_i
|
||||
#define KEY_J SDLK_j
|
||||
#define KEY_K SDLK_k
|
||||
#define KEY_L SDLK_l
|
||||
#define KEY_M SDLK_m
|
||||
#define KEY_N SDLK_n
|
||||
#define KEY_O SDLK_o
|
||||
#define KEY_P SDLK_p
|
||||
#define KEY_Q SDLK_q
|
||||
#define KEY_R SDLK_r
|
||||
#define KEY_S SDLK_s
|
||||
#define KEY_T SDLK_t
|
||||
#define KEY_U SDLK_u
|
||||
#define KEY_V SDLK_v
|
||||
#define KEY_W SDLK_w
|
||||
#define KEY_X SDLK_x
|
||||
#define KEY_Y SDLK_y
|
||||
#define KEY_Z SDLK_z
|
||||
#define KEY_RSHIFT SDLK_RSHIFT
|
||||
#define KEY_LSHIFT SDLK_LSHIFT
|
||||
#define KEY_RCONTROL SDLK_RCTRL
|
||||
#define KEY_LCONTROL SDLK_LCTRL
|
||||
#define KEY_RALT SDLK_RALT
|
||||
#define KEY_LALT SDLK_LALT
|
||||
#define KEY_UP SDLK_UP
|
||||
#define KEY_DOWN SDLK_DOWN
|
||||
#define KEY_LEFT SDLK_LEFT
|
||||
#define KEY_RIGHT SDLK_RIGHT
|
||||
|
||||
#endif
|
91
src/language.cpp
Normal file
91
src/language.cpp
Normal file
|
@ -0,0 +1,91 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "config.hpp"
|
||||
#include "hotkeys.hpp"
|
||||
#include "language.hpp"
|
||||
#include "preferences.hpp"
|
||||
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
std::map<std::string,std::string> string_table;
|
||||
|
||||
std::vector<std::string> get_languages(config& cfg)
|
||||
{
|
||||
std::vector<std::string> res;
|
||||
|
||||
const std::vector<config*>& lang = cfg.children["language"];
|
||||
for(std::vector<config*>::const_iterator i = lang.begin();
|
||||
i != lang.end(); ++i) {
|
||||
res.push_back((*i)->values["language"]);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
namespace {
|
||||
bool internal_set_language(const std::string& locale, config& cfg)
|
||||
{
|
||||
const std::vector<config*>& lang = cfg.children["language"];
|
||||
for(std::vector<config*>::const_iterator i = lang.begin();
|
||||
i != lang.end(); ++i) {
|
||||
if((*i)->values["id"] == locale || (*i)->values["language"] == locale) {
|
||||
|
||||
for(std::map<std::string,std::string>::const_iterator j =
|
||||
(*i)->values.begin(); j != (*i)->values.end(); ++j) {
|
||||
string_table[j->first] = j->second;
|
||||
}
|
||||
|
||||
add_hotkeys(**i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool set_language(const std::string& locale, config& cfg)
|
||||
{
|
||||
string_table.clear();
|
||||
|
||||
//default to English locale first, then set desired locale
|
||||
internal_set_language("en",cfg);
|
||||
return internal_set_language(locale,cfg);
|
||||
}
|
||||
|
||||
std::string get_locale()
|
||||
{
|
||||
//TODO: Add in support for querying the locale on Windows
|
||||
|
||||
const std::string& prefs_locale = preferences::locale();
|
||||
if(prefs_locale.empty() == false) {
|
||||
return prefs_locale;
|
||||
}
|
||||
|
||||
const char* const locale = getenv("LANG");
|
||||
if(locale != NULL && strlen(locale) >= 2) {
|
||||
//we can't pass pointers into the string to the std::string
|
||||
//constructor because some STL implementations don't support
|
||||
//it (*cough* MSVC++6)
|
||||
std::string res(2,'z');
|
||||
res[0] = tolower(locale[0]);
|
||||
res[1] = tolower(locale[1]);
|
||||
return res;
|
||||
}
|
||||
|
||||
std::cerr << "locale could not be determined; defaulting to locale 'en'\n";
|
||||
return "en";
|
||||
}
|
29
src/language.hpp
Normal file
29
src/language.hpp
Normal file
|
@ -0,0 +1,29 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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 LANGUAGE_HPP_INCLUDED
|
||||
#define LANGUAGE_HPP_INCLUDED
|
||||
|
||||
#include "config.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
extern std::map<std::string,std::string> string_table;
|
||||
|
||||
std::vector<std::string> get_languages(config& cfg);
|
||||
bool set_language(const std::string& locale, config& cfg);
|
||||
|
||||
std::string get_locale();
|
||||
|
||||
#endif
|
16
src/log.cpp
Normal file
16
src/log.cpp
Normal file
|
@ -0,0 +1,16 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
#include "log.hpp"
|
||||
|
||||
int scope_logger::indent = 0;
|
62
src/log.hpp
Normal file
62
src/log.hpp
Normal file
|
@ -0,0 +1,62 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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 LOG_HPP_INCLUDED
|
||||
#define LOG_HPP_INCLUDED
|
||||
|
||||
#define LOG_DATA
|
||||
|
||||
#ifdef LOG_DATA
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "SDL.h"
|
||||
|
||||
struct scope_logger
|
||||
{
|
||||
scope_logger(const std::string& str) : ticks_(SDL_GetTicks()), str_(str) {
|
||||
for(int i = 0; i != indent; ++i)
|
||||
std::cerr << " ";
|
||||
++indent;
|
||||
std::cerr << "BEGIN: " << str_ << "\n";
|
||||
}
|
||||
|
||||
~scope_logger() {
|
||||
const int ticks = SDL_GetTicks() - ticks_;
|
||||
--indent;
|
||||
for(int i = 0; i != indent; ++i)
|
||||
std::cerr << " ";
|
||||
std::cerr << "END: " << str_ << " (took " << ticks << "ms)\n";
|
||||
}
|
||||
|
||||
private:
|
||||
int ticks_;
|
||||
std::string str_;
|
||||
static int indent;
|
||||
};
|
||||
|
||||
#define log(a) std::cerr << a << "\n";
|
||||
#define log1(a,b) std::cerr << a << " info: " << b << "\n";
|
||||
#define log2(a,b,c) std::cerr << a << " info: " << b << ", " << c << "\n";
|
||||
|
||||
#define log_scope(a) scope_logger scope_logging_object__(a);
|
||||
|
||||
#else
|
||||
#define log(a)
|
||||
#define log1(a,b)
|
||||
#define log2(a,b,c)
|
||||
|
||||
#define log_scope(a)
|
||||
#endif
|
||||
|
||||
#endif
|
145
src/make_translation.cpp
Normal file
145
src/make_translation.cpp
Normal file
|
@ -0,0 +1,145 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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.
|
||||
*/
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "config.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
void process_config(const std::string& element_name, const config& cfg,
|
||||
std::map<std::string,std::string>& out)
|
||||
{
|
||||
typedef std::pair<string,string> pair;
|
||||
|
||||
for(map<string,vector<config*> >::const_iterator i =
|
||||
cfg.children.begin(); i != cfg.children.end(); ++i) {
|
||||
for(vector<config*>::const_iterator j = i->second.begin();
|
||||
j != i->second.end(); ++j) {
|
||||
process_config(i->first,**j,out);
|
||||
}
|
||||
}
|
||||
|
||||
const map<string,string>& table = cfg.values;
|
||||
const map<string,string>::const_iterator id = table.find("id");
|
||||
|
||||
if(element_name == "message") {
|
||||
const map<string,string>::const_iterator msg = table.find("message");
|
||||
|
||||
if(id == table.end()) {
|
||||
static const std::string dummy;
|
||||
const std::string& text = msg != table.end() ? msg->second:dummy;
|
||||
std::cerr << "message found with no id: " << text << "\n";
|
||||
return;
|
||||
}
|
||||
|
||||
if(msg == table.end()) {
|
||||
std::cerr << "message found with no text\n";
|
||||
return;
|
||||
}
|
||||
|
||||
out.insert(std::pair<string,string>(id->second,msg->second));
|
||||
} else if(element_name == "part") {
|
||||
const map<string,string>::const_iterator msg = table.find("story");
|
||||
if(id == table.end() || msg == table.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
out.insert(std::pair<string,string>(id->second,msg->second));
|
||||
} else if(element_name == "multiplayer" || element_name == "scenario") {
|
||||
map<string,string>::const_iterator msg = table.find("name");
|
||||
if(id == table.end() || msg == table.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
out.insert(std::pair<string,string>(id->second,msg->second));
|
||||
|
||||
if(element_name == "scenario") {
|
||||
msg = table.find("objectives");
|
||||
if(msg != table.end()) {
|
||||
out.insert(std::pair<string,string>(id->second + "_objectives",
|
||||
msg->second));
|
||||
}
|
||||
}
|
||||
} else if(element_name == "language") {
|
||||
const map<string,string>::const_iterator name = table.find("language");
|
||||
if(name != table.end() && name->second == "English") {
|
||||
for(map<string,string>::const_iterator i = table.begin();
|
||||
i != table.end(); ++i) {
|
||||
if(i->first != "language") {
|
||||
out.insert(*i);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if(element_name == "unit") {
|
||||
const map<string,string>::const_iterator name_it = table.find("name");
|
||||
if(name_it == table.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string name = name_it->second;
|
||||
name.erase(std::remove(name.begin(),name.end(),' '),name.end());
|
||||
|
||||
out.insert(std::pair<string,string>(name,name_it->second));
|
||||
|
||||
const map<string,string>::const_iterator description_it =
|
||||
table.find("unit_description");
|
||||
if(description_it != table.end()) {
|
||||
out.insert(std::pair<string,string>(name + "_description",
|
||||
description_it->second));
|
||||
}
|
||||
|
||||
const map<string,string>::const_iterator ability_it =
|
||||
table.find("ability");
|
||||
if(ability_it != table.end()) {
|
||||
out.insert(pair("ability_" + ability_it->second,
|
||||
ability_it->second));
|
||||
}
|
||||
} else if(element_name == "attack") {
|
||||
const map<string,string>::const_iterator name_it=table.find("name");
|
||||
const map<string,string>::const_iterator type_it=table.find("type");
|
||||
const map<string,string>::const_iterator spec_it=table.find("special");
|
||||
if(name_it == table.end() || type_it == table.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
out.insert(pair("weapon_name_" + name_it->second,name_it->second));
|
||||
out.insert(pair("weapon_type_" + type_it->second,type_it->second));
|
||||
|
||||
if(spec_it == table.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
out.insert(pair("weapon_special_" + spec_it->second,spec_it->second));
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
config cfg(preprocess_file("data/game.cfg"));
|
||||
|
||||
map<string,string> table;
|
||||
process_config("",cfg,table);
|
||||
std::cout << "[language]\n\tlanguage=\"Language Name Goes Here\"\n" <<
|
||||
"id=en #language code - English=en, French=fr, etc\n";
|
||||
for(map<string,string>::const_iterator i = table.begin();
|
||||
i != table.end(); ++i) {
|
||||
std::cout << "\t" << i->first << "=\"" << i->second << "\"\n";
|
||||
}
|
||||
std::cout << "[/language]\n";
|
||||
|
||||
return 0;
|
||||
}
|
224
src/map.cpp
Normal file
224
src/map.cpp
Normal file
|
@ -0,0 +1,224 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "map.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
gamemap::location gamemap::location::null_location;
|
||||
|
||||
const std::string& gamemap::terrain_name(gamemap::TERRAIN terrain) const
|
||||
{
|
||||
static const std::string default_val;
|
||||
const std::map<TERRAIN,terrain_type>::const_iterator i =
|
||||
letterToTerrain_.find(terrain);
|
||||
if(i == letterToTerrain_.end())
|
||||
return default_val;
|
||||
else
|
||||
return i->second.name();
|
||||
}
|
||||
|
||||
const std::string& gamemap::underlying_terrain_name(gamemap::TERRAIN terrain) const
|
||||
{
|
||||
static const std::string default_val;
|
||||
const std::map<TERRAIN,terrain_type>::const_iterator i =
|
||||
letterToTerrain_.find(terrain);
|
||||
if(i == letterToTerrain_.end()) {
|
||||
return default_val;
|
||||
} else {
|
||||
if(i->second.is_alias()) {
|
||||
//we could call underlying_terrain_name, but that could allow
|
||||
//infinite recursion with bad data files, so we call terrain_name
|
||||
//to be safe
|
||||
return terrain_name(i->second.type());
|
||||
} else {
|
||||
return i->second.name();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gamemap::TERRAIN gamemap::underlying_terrain(TERRAIN terrain) const
|
||||
{
|
||||
const std::map<TERRAIN,terrain_type>::const_iterator i =
|
||||
letterToTerrain_.find(terrain);
|
||||
if(i == letterToTerrain_.end()) {
|
||||
return terrain;
|
||||
} else {
|
||||
return i->second.type();
|
||||
}
|
||||
}
|
||||
|
||||
gamemap::location::location(config& cfg) : x(-1), y(-1)
|
||||
{
|
||||
const std::string& xstr = cfg.values["x"];
|
||||
const std::string& ystr = cfg.values["y"];
|
||||
|
||||
//the co-ordinates in config files will be 1-based, while we
|
||||
//want them as 0-based
|
||||
if(xstr.empty() == false)
|
||||
x = atoi(xstr.c_str()) - 1;
|
||||
|
||||
if(ystr.empty() == false)
|
||||
y = atoi(ystr.c_str()) - 1;
|
||||
}
|
||||
|
||||
bool gamemap::location::operator==(const gamemap::location& a) const
|
||||
{
|
||||
return x == a.x && y == a.y;
|
||||
}
|
||||
|
||||
bool gamemap::location::operator!=(const gamemap::location& a) const
|
||||
{
|
||||
return !operator==(a);
|
||||
}
|
||||
|
||||
bool gamemap::location::operator<(const gamemap::location& a) const
|
||||
{
|
||||
return x < a.x || x == a.x && y < a.y;
|
||||
}
|
||||
|
||||
gamemap::location gamemap::location::get_direction(
|
||||
gamemap::location::DIRECTION dir) const
|
||||
{
|
||||
switch(dir) {
|
||||
case NORTH: return gamemap::location(x,y-1);
|
||||
case NORTH_EAST: return gamemap::location(x+1,y-((x%2) == 0 ? 1:0));
|
||||
case SOUTH_EAST: return gamemap::location(x+1,y+((x%2) == 0 ? 0:1));
|
||||
case SOUTH: return gamemap::location(x,y+1);
|
||||
case SOUTH_WEST: return gamemap::location(x-1,y+((x%2) == 0 ? 0:1));
|
||||
case NORTH_WEST: return gamemap::location(x-1,y+((x%2) == 0 ? 1:0));
|
||||
default:
|
||||
assert(false);
|
||||
return gamemap::location();
|
||||
}
|
||||
}
|
||||
|
||||
gamemap::gamemap(config& cfg, const std::string& data) : tiles_(1)
|
||||
{
|
||||
std::vector<config*>& terrains = cfg.children["terrain"];
|
||||
create_terrain_maps(terrains,terrainPrecedence_,letterToTerrain_,terrain_);
|
||||
|
||||
int x = 0, y = 0;
|
||||
for(std::string::const_iterator i = data.begin(); i != data.end(); ++i) {
|
||||
char c = *i;
|
||||
if(c == '\n') {
|
||||
tiles_.push_back(std::vector<TERRAIN>());
|
||||
y = 0;
|
||||
++x;
|
||||
} else {
|
||||
if(letterToTerrain_.count(c) == 0) {
|
||||
if(isdigit(*i)) {
|
||||
startingPositions_[c - '0'] = location(x,y);
|
||||
c = CASTLE;
|
||||
} else {
|
||||
std::cerr << "Illegal character in map: '" << c << "'\n";
|
||||
throw incorrect_format_exception("Illegal character");
|
||||
}
|
||||
}
|
||||
|
||||
if(c == TOWER) {
|
||||
towers_.push_back(location(x,y));
|
||||
}
|
||||
|
||||
tiles_.back().push_back(c);
|
||||
|
||||
++y;
|
||||
}
|
||||
}
|
||||
|
||||
if(tiles_.empty())
|
||||
throw incorrect_format_exception("empty map");
|
||||
|
||||
for(int n = 0; n != tiles_.size(); ++n) {
|
||||
if(tiles_[n].size() != this->y()) {
|
||||
tiles_.erase(tiles_.begin()+n);
|
||||
--n;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string gamemap::write() const
|
||||
{
|
||||
std::stringstream str;
|
||||
for(int i = 0; i != x(); ++i) {
|
||||
for(int j = 0; j != y(); ++j) {
|
||||
int n;
|
||||
for(n = 0; n != 10; ++n) {
|
||||
if(startingPositions_[n] == location(i,j))
|
||||
break;
|
||||
}
|
||||
|
||||
if(n < 10)
|
||||
str << n;
|
||||
else
|
||||
str << tiles_[i][j];
|
||||
}
|
||||
|
||||
str << "\n";
|
||||
}
|
||||
|
||||
return str.str();
|
||||
}
|
||||
|
||||
int gamemap::x() const { return tiles_.size(); }
|
||||
int gamemap::y() const { return tiles_[0].size(); }
|
||||
|
||||
const std::vector<gamemap::TERRAIN>& gamemap::operator[](int index) const
|
||||
{
|
||||
return tiles_[index];
|
||||
}
|
||||
|
||||
const gamemap::location& gamemap::starting_position(int n) const
|
||||
{
|
||||
return startingPositions_[n];
|
||||
}
|
||||
|
||||
int gamemap::num_starting_positions() const
|
||||
{
|
||||
return sizeof(startingPositions_)/sizeof(*startingPositions_);
|
||||
}
|
||||
|
||||
bool gamemap::is_starting_position(const gamemap::location& loc) const
|
||||
{
|
||||
const gamemap::location* const end
|
||||
= startingPositions_+num_starting_positions();
|
||||
return std::find(startingPositions_,end,loc) != end;
|
||||
}
|
||||
|
||||
const terrain_type& gamemap::get_terrain_info(TERRAIN terrain) const
|
||||
{
|
||||
static const terrain_type default_terrain;
|
||||
const std::map<TERRAIN,terrain_type>::const_iterator i =
|
||||
letterToTerrain_.find(terrain);
|
||||
if(i != letterToTerrain_.end())
|
||||
return i->second;
|
||||
else
|
||||
return default_terrain;
|
||||
}
|
||||
|
||||
const std::vector<gamemap::TERRAIN>& gamemap::get_terrain_precedence() const
|
||||
{
|
||||
return terrainPrecedence_;
|
||||
}
|
||||
|
||||
void gamemap::set_terrain(const gamemap::location& loc, gamemap::TERRAIN ter)
|
||||
{
|
||||
if(!on_board(loc))
|
||||
return;
|
||||
|
||||
tiles_[loc.x][loc.y] = ter;
|
||||
}
|
101
src/map.hpp
Normal file
101
src/map.hpp
Normal file
|
@ -0,0 +1,101 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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 MAP_H_INCLUDED
|
||||
#define MAP_H_INCLUDED
|
||||
|
||||
#include "config.hpp"
|
||||
#include "terrain.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class gamemap
|
||||
{
|
||||
public:
|
||||
|
||||
typedef char TERRAIN;
|
||||
|
||||
enum { VOID_TERRAIN = ' ', CASTLE = 'C', TOWER = 't', FOREST = 'f' };
|
||||
|
||||
//the name of the terrain is the terrain itself, the underlying terrain
|
||||
//is the name of the terrain for game-logic purposes. I.e. if the terrain
|
||||
//is simply an alias, the underlying terrain name is the name of the
|
||||
//terrain that it's aliased to
|
||||
const std::string& terrain_name(TERRAIN terrain) const;
|
||||
const std::string& underlying_terrain_name(TERRAIN terrain) const;
|
||||
TERRAIN underlying_terrain(TERRAIN terrain) const;
|
||||
|
||||
struct incorrect_format_exception {
|
||||
incorrect_format_exception(const char* msg) : msg_(msg) {}
|
||||
const char* const msg_;
|
||||
};
|
||||
|
||||
struct location {
|
||||
enum DIRECTION { NORTH, NORTH_EAST, SOUTH_EAST, SOUTH,
|
||||
SOUTH_WEST, NORTH_WEST };
|
||||
|
||||
location() : x(-1), y(-1) {}
|
||||
location(int x, int y) : x(x), y(y) {}
|
||||
location(config& cfg);
|
||||
|
||||
bool valid() const { return x >= 0 && y >= 0; }
|
||||
|
||||
int x, y;
|
||||
|
||||
bool operator<(const location& a) const;
|
||||
bool operator==(const location& a) const;
|
||||
bool operator!=(const location& a) const;
|
||||
|
||||
location get_direction(DIRECTION d) const;
|
||||
|
||||
static location null_location;
|
||||
};
|
||||
|
||||
gamemap(config& cfg,
|
||||
const std::string& data); //throw(incorrect_format_exception)
|
||||
|
||||
std::string write() const;
|
||||
|
||||
int x() const;
|
||||
int y() const;
|
||||
|
||||
const std::vector<TERRAIN>& operator[](int index) const;
|
||||
|
||||
const location& starting_position(int n) const;
|
||||
int num_starting_positions() const;
|
||||
bool is_starting_position(const location& loc) const;
|
||||
|
||||
bool on_board(const location& loc) const
|
||||
{
|
||||
return loc.valid() && loc.x < x() && loc.y < y();
|
||||
}
|
||||
|
||||
const std::vector<location>& towers() const { return towers_; }
|
||||
|
||||
const terrain_type& get_terrain_info(TERRAIN terrain) const;
|
||||
|
||||
const std::vector<TERRAIN>& get_terrain_precedence() const;
|
||||
|
||||
void set_terrain(const location& loc, TERRAIN ter);
|
||||
private:
|
||||
std::vector<TERRAIN> terrainPrecedence_;
|
||||
std::map<TERRAIN,terrain_type> letterToTerrain_;
|
||||
std::map<std::string,terrain_type> terrain_;
|
||||
|
||||
std::vector<std::vector<TERRAIN> > tiles_;
|
||||
std::vector<location> towers_;
|
||||
location startingPositions_[10];
|
||||
};
|
||||
|
||||
#endif
|
910
src/menu.cpp
Normal file
910
src/menu.cpp
Normal file
|
@ -0,0 +1,910 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "config.hpp"
|
||||
#include "font.hpp"
|
||||
#include "language.hpp"
|
||||
#include "menu.hpp"
|
||||
#include "playlevel.hpp"
|
||||
#include "language.hpp"
|
||||
#include "sdl_utils.hpp"
|
||||
#include "widgets/button.hpp"
|
||||
#include "widgets/textbox.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <numeric>
|
||||
|
||||
namespace gui {
|
||||
|
||||
void draw_dialog_frame(int x, int y, int w, int h, display& disp)
|
||||
{
|
||||
const int border_size = 6;
|
||||
|
||||
SDL_Surface* const scr = disp.video().getSurface();
|
||||
|
||||
const display::Pixel border_colour = SDL_MapRGB(scr->format,200,0,0);
|
||||
|
||||
draw_solid_tinted_rectangle(x-border_size,y-border_size,
|
||||
w+border_size,h+border_size,0,0,0,0.6,scr);
|
||||
draw_solid_tinted_rectangle(x,y,w+border_size,h+border_size,0,0,0,0.6,scr);
|
||||
|
||||
draw_rectangle(x-border_size,y-border_size,w+border_size,h+border_size,
|
||||
border_colour,scr);
|
||||
draw_rectangle(x,y,w+border_size,h+border_size,border_colour,scr);
|
||||
|
||||
SDL_Rect update = {x-border_size,y-border_size,
|
||||
w+border_size*2+1,h+border_size*2+1};
|
||||
disp.update_rect(update);
|
||||
}
|
||||
|
||||
void draw_rectangle(int x, int y, int w, int h, short colour,
|
||||
SDL_Surface* target)
|
||||
{
|
||||
if(x < 0 || y < 0 || x+w >= target->w || y+h >= target->h) {
|
||||
std::cerr << "Rectangle has illegal co-ordinates: " << x << "," << y
|
||||
<< "," << w << "," << h << "\n";
|
||||
return;
|
||||
}
|
||||
|
||||
short* top_left = reinterpret_cast<short*>(target->pixels) + target->w*y+x;
|
||||
short* top_right = top_left + w;
|
||||
short* bot_left = top_left + target->w*h;
|
||||
short* bot_right = bot_left + w;
|
||||
|
||||
std::fill(top_left,top_right+1,colour);
|
||||
std::fill(bot_left,bot_right+1,colour);
|
||||
while(top_left != bot_left) {
|
||||
*top_left = colour;
|
||||
*top_right = colour;
|
||||
top_left += target->w;
|
||||
top_right += target->w;
|
||||
}
|
||||
}
|
||||
|
||||
void draw_solid_tinted_rectangle(int x, int y, int w, int h,
|
||||
int r, int g, int b,
|
||||
double alpha, SDL_Surface* target)
|
||||
{
|
||||
if(x < 0 || y < 0 || x+w >= target->w || y+h >= target->h) {
|
||||
std::cerr << "Rectangle has illegal co-ordinates: " << x << "," << y
|
||||
<< "," << w << "," << h << "\n";
|
||||
return;
|
||||
}
|
||||
|
||||
const SDL_PixelFormat* const fmt = target->format;
|
||||
short* p = reinterpret_cast<short*>(target->pixels) + target->w*y + x;
|
||||
while(h > 0) {
|
||||
short* beg = p;
|
||||
short* const end = p + w;
|
||||
while(beg != end) {
|
||||
const int cur_r = ((*beg&fmt->Rmask) >> fmt->Rshift) << fmt->Rloss;
|
||||
const int cur_g = ((*beg&fmt->Gmask) >> fmt->Gshift) << fmt->Gloss;
|
||||
const int cur_b = ((*beg&fmt->Bmask) >> fmt->Bshift) << fmt->Bloss;
|
||||
|
||||
const int new_r = int(double(cur_r)*(1.0-alpha) + double(r)*alpha);
|
||||
const int new_g = int(double(cur_g)*(1.0-alpha) + double(g)*alpha);
|
||||
const int new_b = int(double(cur_b)*(1.0-alpha) + double(b)*alpha);
|
||||
|
||||
*beg = ((new_r >> fmt->Rloss) << fmt->Rshift) |
|
||||
((new_g >> fmt->Gloss) << fmt->Gshift) |
|
||||
((new_b >> fmt->Bloss) << fmt->Bshift);
|
||||
|
||||
++beg;
|
||||
}
|
||||
|
||||
p += target->w;
|
||||
--h;
|
||||
}
|
||||
}
|
||||
|
||||
} //end namespace gui
|
||||
|
||||
namespace {
|
||||
const int max_menu_items = 18;
|
||||
const int menu_font_size = 16;
|
||||
const int menu_cell_padding = 10;
|
||||
|
||||
class menu
|
||||
{
|
||||
display* display_;
|
||||
int x_, y_;
|
||||
std::vector<std::vector<std::string> > items_;
|
||||
mutable std::vector<int> column_widths_;
|
||||
|
||||
scoped_sdl_surface buffer_;
|
||||
int selected_;
|
||||
bool click_selects_;
|
||||
bool previous_button_;
|
||||
bool drawn_;
|
||||
|
||||
mutable int height_;
|
||||
mutable int width_;
|
||||
|
||||
mutable int first_item_on_screen_;
|
||||
gui::button uparrow_, downarrow_;
|
||||
|
||||
const std::vector<int>& column_widths() const
|
||||
{
|
||||
if(column_widths_.empty()) {
|
||||
for(int row = 0; row != items_.size(); ++row) {
|
||||
for(int col = 0; col != items_[row].size(); ++col) {
|
||||
static const SDL_Rect area =
|
||||
{0,0,display_->x(),display_->y()};
|
||||
|
||||
const SDL_Rect res =
|
||||
font::draw_text(NULL,area,menu_font_size,
|
||||
font::NORMAL_COLOUR,items_[row][col],x_,y_);
|
||||
|
||||
if(col == column_widths_.size()) {
|
||||
column_widths_.push_back(res.w + menu_cell_padding);
|
||||
} else if(res.w > column_widths_[col] - menu_cell_padding) {
|
||||
column_widths_[col] = res.w + menu_cell_padding;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return column_widths_;
|
||||
}
|
||||
|
||||
void draw_item(int item) {
|
||||
SDL_Rect rect = get_item_rect(item);
|
||||
if(rect.w == 0)
|
||||
return;
|
||||
|
||||
short* const dstptr = reinterpret_cast<short*>(
|
||||
display_->video().getSurface()->pixels) +
|
||||
rect.y*display_->x() + x_;
|
||||
|
||||
if(buffer_.get() != NULL) {
|
||||
const int ypos = items_start()+(item-first_item_on_screen_)*rect.h;
|
||||
SDL_Rect srcrect = {0,ypos,rect.w,rect.h};
|
||||
SDL_BlitSurface(buffer_,&srcrect,
|
||||
display_->video().getSurface(),&rect);
|
||||
}
|
||||
|
||||
short* dst = dstptr;
|
||||
|
||||
gui::draw_solid_tinted_rectangle(x_,rect.y,width(),rect.h,
|
||||
item == selected_ ? 150:0,0,0,
|
||||
0.7,display_->video().getSurface());
|
||||
|
||||
SDL_Rect area = {0,0,display_->x(),display_->y()};
|
||||
|
||||
const std::vector<int>& widths = column_widths();
|
||||
|
||||
int xpos = rect.x;
|
||||
for(int i = 0; i != items_[item].size(); ++i) {
|
||||
font::draw_text(display_,area,menu_font_size,font::NORMAL_COLOUR,
|
||||
items_[item][i],xpos,rect.y);
|
||||
xpos += widths[i];
|
||||
}
|
||||
}
|
||||
|
||||
void draw() {
|
||||
drawn_ = true;
|
||||
|
||||
for(int i = 0; i != items_.size(); ++i)
|
||||
draw_item(i);
|
||||
|
||||
display_->video().update(x_,y_,width(),height());
|
||||
}
|
||||
|
||||
int hit(int x, int y) const {
|
||||
if(x > x_ && x < x_ + width() && y > y_ && y < y_ + height()){
|
||||
for(int i = 0; i != items_.size(); ++i) {
|
||||
const SDL_Rect& rect = get_item_rect(i);
|
||||
if(y > rect.y && y < rect.y + rect.h)
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
mutable std::map<int,SDL_Rect> itemRects_;
|
||||
|
||||
SDL_Rect get_item_rect(int item) const
|
||||
{
|
||||
const SDL_Rect empty_rect = {0,0,0,0};
|
||||
if(item < first_item_on_screen_ ||
|
||||
item >= first_item_on_screen_ + max_menu_items) {
|
||||
return empty_rect;
|
||||
}
|
||||
|
||||
const std::map<int,SDL_Rect>::const_iterator i = itemRects_.find(item);
|
||||
if(i != itemRects_.end())
|
||||
return i->second;
|
||||
|
||||
int y = y_ + items_start();
|
||||
if(item != first_item_on_screen_) {
|
||||
const SDL_Rect& prev = get_item_rect(item-1);
|
||||
y = prev.y + prev.h;
|
||||
}
|
||||
|
||||
static const SDL_Rect area = {0,0,display_->x(),display_->y()};
|
||||
|
||||
SDL_Rect res = font::draw_text(NULL,area,menu_font_size,
|
||||
font::NORMAL_COLOUR,items_[item][0],x_,y);
|
||||
|
||||
res.w = width();
|
||||
|
||||
//only insert into the cache if the menu's co-ordinates have
|
||||
//been initialized
|
||||
if(x_ > 0 && y_ > 0)
|
||||
itemRects_.insert(std::pair<int,SDL_Rect>(item,res));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
int items_start() const
|
||||
{
|
||||
if(items_.size() > max_menu_items)
|
||||
return uparrow_.height();
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
int items_end() const
|
||||
{
|
||||
if(items_.size() > max_menu_items)
|
||||
return height() - downarrow_.height();
|
||||
else
|
||||
return height();
|
||||
}
|
||||
|
||||
int items_height() const
|
||||
{
|
||||
return items_end() - items_start();
|
||||
}
|
||||
|
||||
public:
|
||||
menu(display& disp, const std::vector<std::string>& items,
|
||||
bool click_selects=false)
|
||||
: display_(&disp), x_(0), y_(0), buffer_(NULL),
|
||||
selected_(-1), click_selects_(click_selects),
|
||||
previous_button_(true), drawn_(false), height_(-1), width_(-1),
|
||||
first_item_on_screen_(0),
|
||||
uparrow_(disp,"",gui::button::TYPE_PRESS,"uparrow"),
|
||||
downarrow_(disp,"",gui::button::TYPE_PRESS,"downarrow")
|
||||
{
|
||||
for(std::vector<std::string>::const_iterator item = items.begin();
|
||||
item != items.end(); ++item) {
|
||||
items_.push_back(config::split(*item));
|
||||
|
||||
//make sure there is always at least one item
|
||||
if(items_.back().empty())
|
||||
items_.back().push_back(" ");
|
||||
}
|
||||
}
|
||||
|
||||
int height() const {
|
||||
if(height_ == -1) {
|
||||
const SDL_Rect area = { 0, 0, display_->x(), display_->y() };
|
||||
height_ = 0;
|
||||
for(int i = 0; i != items_.size() && i != max_menu_items; ++i) {
|
||||
height_ += get_item_rect(i).h;
|
||||
}
|
||||
|
||||
if(items_.size() > max_menu_items) {
|
||||
height_ += uparrow_.height() + downarrow_.height();
|
||||
}
|
||||
}
|
||||
|
||||
return height_;
|
||||
}
|
||||
|
||||
int width() const {
|
||||
if(width_ == -1) {
|
||||
const std::vector<int>& widths = column_widths();
|
||||
width_ = std::accumulate(widths.begin(),widths.end(),0);
|
||||
}
|
||||
|
||||
return width_;
|
||||
}
|
||||
|
||||
int selection() const { return selected_; }
|
||||
|
||||
void set_loc(int x, int y) {
|
||||
x_ = x;
|
||||
y_ = y;
|
||||
|
||||
const int w = width();
|
||||
|
||||
SDL_Rect portion = {x_,y_,w,height()};
|
||||
SDL_Surface* const screen = display_->video().getSurface();
|
||||
buffer_.assign(get_surface_portion(screen, portion));
|
||||
|
||||
if(items_.size() > max_menu_items) {
|
||||
uparrow_.set_x(x_);
|
||||
uparrow_.set_y(y_);
|
||||
downarrow_.set_x(x_);
|
||||
downarrow_.set_y(y_+items_end());
|
||||
}
|
||||
}
|
||||
|
||||
int process(int x, int y, bool button,bool up_arrow,bool down_arrow,
|
||||
bool page_up, bool page_down) {
|
||||
if(items_.size() > max_menu_items) {
|
||||
const bool up = uparrow_.process(x,y,button);
|
||||
if(up && first_item_on_screen_ > 0) {
|
||||
itemRects_.clear();
|
||||
--first_item_on_screen_;
|
||||
|
||||
draw();
|
||||
}
|
||||
|
||||
const bool down = downarrow_.process(x,y,button);
|
||||
if(down && first_item_on_screen_ + max_menu_items < items_.size()) {
|
||||
itemRects_.clear();
|
||||
++first_item_on_screen_;
|
||||
draw();
|
||||
}
|
||||
}
|
||||
|
||||
if(up_arrow && !click_selects_ && selected_ > 0) {
|
||||
--selected_;
|
||||
if(selected_ < first_item_on_screen_) {
|
||||
itemRects_.clear();
|
||||
first_item_on_screen_ = selected_;
|
||||
}
|
||||
|
||||
draw();
|
||||
}
|
||||
|
||||
if(down_arrow && !click_selects_ && selected_ < items_.size()-1) {
|
||||
++selected_;
|
||||
if(selected_ - first_item_on_screen_ == max_menu_items) {
|
||||
itemRects_.clear();
|
||||
++first_item_on_screen_;
|
||||
}
|
||||
|
||||
draw();
|
||||
}
|
||||
|
||||
if(page_up && !click_selects_) {
|
||||
selected_ -= max_menu_items;
|
||||
if(selected_ < 0)
|
||||
selected_ = 0;
|
||||
|
||||
itemRects_.clear();
|
||||
first_item_on_screen_ = selected_;
|
||||
|
||||
draw();
|
||||
}
|
||||
|
||||
if(page_down && !click_selects_) {
|
||||
selected_ += max_menu_items;
|
||||
if(selected_ >= items_.size())
|
||||
selected_ = items_.size()-1;
|
||||
|
||||
first_item_on_screen_ = selected_ - (max_menu_items-1);
|
||||
if(first_item_on_screen_ < 0)
|
||||
first_item_on_screen_ = 0;
|
||||
|
||||
itemRects_.clear();
|
||||
|
||||
draw();
|
||||
}
|
||||
|
||||
const int starting_selected = selected_;
|
||||
|
||||
const int hit_item = hit(x,y);
|
||||
|
||||
if(click_selects_) {
|
||||
selected_ = hit_item;
|
||||
if(button && !previous_button_)
|
||||
return selected_;
|
||||
else {
|
||||
if(!drawn_ || selected_ != starting_selected)
|
||||
draw();
|
||||
previous_button_ = button;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if(button && hit_item != -1){
|
||||
selected_ = hit_item;
|
||||
}
|
||||
|
||||
if(selected_ == -1)
|
||||
selected_ = 0;
|
||||
|
||||
if(selected_ != starting_selected)
|
||||
draw();
|
||||
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace gui
|
||||
{
|
||||
|
||||
int show_dialog(display& disp, SDL_Surface* image,
|
||||
const std::string& caption, const std::string& msg,
|
||||
DIALOG_TYPE type,
|
||||
const std::vector<std::string>* menu_items_ptr,
|
||||
const std::vector<unit>* units_ptr,
|
||||
const std::string& text_widget_label,
|
||||
std::string* text_widget_text)
|
||||
{
|
||||
if(disp.update_locked())
|
||||
return -1;
|
||||
|
||||
const std::vector<std::string>& menu_items =
|
||||
(menu_items_ptr == NULL) ? std::vector<std::string>() : *menu_items_ptr;
|
||||
const std::vector<unit>& units =
|
||||
(units_ptr == NULL) ? std::vector<unit>() : *units_ptr;
|
||||
|
||||
static const int message_font_size = 16;
|
||||
static const int caption_font_size = 18;
|
||||
|
||||
CVideo& screen = disp.video();
|
||||
SDL_Surface* const scr = screen.getSurface();
|
||||
|
||||
SDL_Rect clipRect = { 0, 0, disp.x(), disp.y() };
|
||||
|
||||
const bool use_textbox = text_widget_text != NULL;
|
||||
static const std::string default_text_string = "";
|
||||
const unsigned int text_box_width = 350;
|
||||
textbox text_widget(disp,text_box_width,
|
||||
use_textbox ? *text_widget_text : default_text_string);
|
||||
|
||||
int text_widget_width = 0;
|
||||
int text_widget_height = 0;
|
||||
if(use_textbox) {
|
||||
text_widget_width =
|
||||
font::draw_text(NULL, clipRect, message_font_size,
|
||||
font::NORMAL_COLOUR, text_widget_label, 0, 0, NULL).w +
|
||||
text_widget.width();
|
||||
text_widget_height = text_widget.height();
|
||||
}
|
||||
|
||||
menu menu_(disp,menu_items,type == MESSAGE);
|
||||
|
||||
const int border_size = 6;
|
||||
const short text_colour = 0xFFFF;
|
||||
const short border_colour = 0xF000;
|
||||
int nlines = 1;
|
||||
int longest_line = 0;
|
||||
int cur_line = 0;
|
||||
|
||||
const int max_line_length = 58;
|
||||
|
||||
std::string message = msg;
|
||||
for(std::string::iterator message_it = message.begin();
|
||||
message_it != message.end(); ++message_it) {
|
||||
if(*message_it == ' ' && cur_line > max_line_length)
|
||||
*message_it = '\n';
|
||||
|
||||
if(*message_it == '\n') {
|
||||
if(cur_line > longest_line)
|
||||
longest_line = cur_line;
|
||||
|
||||
cur_line = 0;
|
||||
|
||||
++nlines;
|
||||
} else {
|
||||
++cur_line;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_Rect text_size = { 0, 0, 0, 0 };
|
||||
if(!message.empty()) {
|
||||
text_size = font::draw_text(NULL, clipRect, message_font_size,
|
||||
font::NORMAL_COLOUR, message, 0, 0, NULL);
|
||||
}
|
||||
|
||||
SDL_Rect caption_size = { 0, 0, 0, 0 };
|
||||
if(!caption.empty()) {
|
||||
caption_size = font::draw_text(NULL, clipRect, caption_font_size,
|
||||
font::NORMAL_COLOUR,caption,0,0,NULL);
|
||||
}
|
||||
|
||||
const std::string* button_list = NULL;
|
||||
std::vector<button> buttons;
|
||||
switch(type) {
|
||||
case MESSAGE:
|
||||
break;
|
||||
case OK_ONLY: {
|
||||
static const std::string thebuttons[] = { "ok_button", "" };
|
||||
button_list = thebuttons;
|
||||
break;
|
||||
}
|
||||
|
||||
case YES_NO: {
|
||||
static const std::string thebuttons[] = { "yes_button",
|
||||
"no_button", ""};
|
||||
button_list = thebuttons;
|
||||
break;
|
||||
}
|
||||
|
||||
case OK_CANCEL: {
|
||||
static const std::string thebuttons[] = { "ok_button",
|
||||
"cancel_button",""};
|
||||
button_list = thebuttons;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const int button_height_padding = 10;
|
||||
int button_width_padding = 0;
|
||||
int button_heights = 0;
|
||||
int button_widths = 0;
|
||||
if(button_list != NULL) {
|
||||
try {
|
||||
while(button_list->empty() == false) {
|
||||
buttons.push_back(button(disp,string_table[*button_list]));
|
||||
|
||||
if(buttons.back().height() > button_heights)
|
||||
button_heights = buttons.back().height();
|
||||
|
||||
button_widths += buttons.back().width();
|
||||
button_width_padding += 5;
|
||||
|
||||
++button_list;
|
||||
}
|
||||
|
||||
} catch(button::error&) {
|
||||
std::cerr << "error initializing button!\n";
|
||||
}
|
||||
}
|
||||
|
||||
if(button_heights > 0) {
|
||||
button_heights += button_height_padding;
|
||||
}
|
||||
|
||||
if(cur_line > longest_line)
|
||||
longest_line = cur_line;
|
||||
|
||||
const int left_padding = 10;
|
||||
const int right_padding = 10;
|
||||
const int image_h_padding = image != NULL ? 10 : 0;
|
||||
const int top_padding = 10;
|
||||
const int bottom_padding = 10;
|
||||
const int padding_width = left_padding + right_padding + image_h_padding;
|
||||
const int padding_height = top_padding + bottom_padding;
|
||||
const int caption_width = caption_size.w;
|
||||
const int image_width = image != NULL ? image->w : 0;
|
||||
const int total_image_width = caption_width > image_width ?
|
||||
caption_width : image_width;
|
||||
const int image_height = image != NULL ? image->h : 0;
|
||||
|
||||
int text_width = text_size.w;
|
||||
if(menu_.width() > text_width)
|
||||
text_width = menu_.width();
|
||||
|
||||
int total_width = total_image_width + text_width +
|
||||
padding_width;
|
||||
if(button_widths + button_width_padding > total_width)
|
||||
total_width = button_widths + button_width_padding;
|
||||
|
||||
if(text_widget_width+left_padding+right_padding > total_width)
|
||||
total_width = text_widget_width+left_padding+right_padding;
|
||||
|
||||
const int total_height = (image_height+8 > text_size.h ?
|
||||
image_height+8 : text_size.h) +
|
||||
padding_height + button_heights + menu_.height() +
|
||||
text_widget_height;
|
||||
|
||||
if(total_width > scr->w - 100 || total_height > scr->h - 100)
|
||||
return false;
|
||||
|
||||
int xloc = scr->w/2 - total_width/2;
|
||||
int yloc = scr->h/2 - total_height/2;
|
||||
|
||||
int unitx = 0;
|
||||
int unity = 0;
|
||||
//if we are showing a dialog with unit details, then we have
|
||||
//to make more room for it
|
||||
if(!units.empty()) {
|
||||
xloc += scr->w/10;
|
||||
unitx = xloc - 300;
|
||||
if(unitx < 10)
|
||||
unitx = 10;
|
||||
|
||||
unity = yloc;
|
||||
}
|
||||
|
||||
//make sure that the dialog doesn't overlap the right part of the screen
|
||||
if(xloc + total_width+border_size >= disp.mapx()-1) {
|
||||
xloc = disp.mapx()-(total_width+border_size+2);
|
||||
if(xloc < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
const int button_hpadding = total_width - button_widths;
|
||||
int button_offset = 0;
|
||||
for(int button_num = 0; button_num != buttons.size(); ++button_num) {
|
||||
const int padding_amount = button_hpadding/(buttons.size()+1);
|
||||
buttons[button_num].set_x(xloc + padding_amount*(button_num+1) +
|
||||
button_offset);
|
||||
buttons[button_num].set_y(yloc + total_height - button_heights);
|
||||
button_offset += buttons[button_num].width();
|
||||
}
|
||||
|
||||
if(menu_.height() > 0)
|
||||
menu_.set_loc(xloc+total_image_width+left_padding+image_h_padding,
|
||||
yloc+top_padding+text_size.h);
|
||||
|
||||
draw_dialog_frame(xloc,yloc,total_width,total_height,disp);
|
||||
|
||||
if(image != NULL) {
|
||||
const int x = xloc + left_padding;
|
||||
const int y = yloc + top_padding;
|
||||
|
||||
disp.blit_surface(x,y,image);
|
||||
|
||||
int center_font = 0;
|
||||
if(caption_size.w < image->w) {
|
||||
center_font = image->w/2 - caption_size.w/2;
|
||||
}
|
||||
|
||||
font::draw_text(&disp, clipRect, caption_font_size,
|
||||
font::NORMAL_COLOUR, caption,
|
||||
xloc+left_padding+center_font,
|
||||
yloc+top_padding+image->h-6, NULL);
|
||||
}
|
||||
|
||||
if(!units.empty()) {
|
||||
const int unitw = 200;
|
||||
const int unith = disp.y()/2;
|
||||
draw_solid_tinted_rectangle(unitx,unity,unitw,unith,
|
||||
0,0,0,1.0,scr);
|
||||
draw_rectangle(unitx,unity,unitw,unith,border_colour,scr);
|
||||
}
|
||||
|
||||
font::draw_text(&disp, clipRect, message_font_size,
|
||||
font::NORMAL_COLOUR, message,
|
||||
xloc+total_image_width+left_padding+image_h_padding,
|
||||
yloc+top_padding);
|
||||
|
||||
if(use_textbox) {
|
||||
const int image_h = image != NULL ? image->h : 0;
|
||||
const int text_widget_y = yloc+top_padding+image_h-6+text_size.h;
|
||||
text_widget.set_location(xloc + left_padding +
|
||||
text_widget_width - text_widget.width(),
|
||||
text_widget_y);
|
||||
text_widget.draw();
|
||||
font::draw_text(&disp, clipRect, message_font_size,
|
||||
font::NORMAL_COLOUR, text_widget_label,
|
||||
xloc + left_padding,text_widget_y);
|
||||
}
|
||||
|
||||
screen.update(0,0,scr->w,scr->h);
|
||||
|
||||
CKey key;
|
||||
|
||||
bool left_button = true, right_button = true, key_down = true,
|
||||
up_arrow = false, down_arrow = false,
|
||||
page_up = false, page_down = false;
|
||||
|
||||
disp.invalidate_all();
|
||||
|
||||
int cur_selection = -1;
|
||||
|
||||
SDL_Rect unit_details_rect, unit_profile_rect;
|
||||
unit_details_rect.w = 0;
|
||||
unit_profile_rect.w = 0;
|
||||
|
||||
bool first_time = true;
|
||||
|
||||
for(;;) {
|
||||
int mousex, mousey;
|
||||
const int mouse_flags = SDL_GetMouseState(&mousex,&mousey);
|
||||
|
||||
const bool new_right_button = mouse_flags&SDL_BUTTON_RMASK;
|
||||
const bool new_left_button = mouse_flags&SDL_BUTTON_LMASK;
|
||||
const bool new_key_down = key[KEY_SPACE] || key[KEY_ENTER] ||
|
||||
key[KEY_ESCAPE];
|
||||
|
||||
const bool new_up_arrow = key[KEY_UP];
|
||||
const bool new_down_arrow = key[KEY_DOWN];
|
||||
|
||||
const bool new_page_up = key[SDLK_PAGEUP];
|
||||
const bool new_page_down = key[SDLK_PAGEDOWN];
|
||||
|
||||
|
||||
if(!key_down && key[KEY_ENTER] &&
|
||||
(type == YES_NO || type == OK_CANCEL)) {
|
||||
if(menu_.height() == 0) {
|
||||
return 0;
|
||||
} else {
|
||||
return menu_.selection();
|
||||
}
|
||||
}
|
||||
|
||||
if(menu_.selection() != cur_selection || first_time) {
|
||||
cur_selection = menu_.selection();
|
||||
|
||||
int selection = cur_selection;
|
||||
if(first_time)
|
||||
selection = 0;
|
||||
|
||||
if(selection >= 0 && selection < units.size()) {
|
||||
SDL_Surface* const screen = disp.video().getSurface();
|
||||
if(unit_details_rect.w > 0) {
|
||||
SDL_FillRect(screen,&unit_details_rect,0);
|
||||
disp.update_rect(unit_details_rect);
|
||||
}
|
||||
|
||||
if(unit_profile_rect.w > 0) {
|
||||
SDL_FillRect(screen,&unit_profile_rect,0);
|
||||
disp.update_rect(unit_profile_rect);
|
||||
}
|
||||
|
||||
disp.draw_unit_details(unitx+left_padding,
|
||||
unity+top_padding, gamemap::location(), units[selection],
|
||||
unit_details_rect, unit_profile_rect);
|
||||
disp.update_display();
|
||||
}
|
||||
}
|
||||
|
||||
first_time = false;
|
||||
|
||||
if(menu_.height() > 0) {
|
||||
const int res = menu_.process(mousex,mousey,new_left_button,
|
||||
!up_arrow && new_up_arrow,
|
||||
!down_arrow && new_down_arrow,
|
||||
!page_up && new_page_up,
|
||||
!page_down && new_page_down);
|
||||
if(res != -1)
|
||||
return res;
|
||||
}
|
||||
|
||||
up_arrow = new_up_arrow;
|
||||
down_arrow = new_down_arrow;
|
||||
page_up = new_page_up;
|
||||
page_down = new_page_down;
|
||||
|
||||
if(use_textbox) {
|
||||
text_widget.process();
|
||||
}
|
||||
|
||||
|
||||
if(buttons.empty() && (new_left_button && !left_button ||
|
||||
new_right_button && !right_button) ||
|
||||
buttons.size() < 2 && new_key_down && !key_down &&
|
||||
menu_.height() == 0)
|
||||
break;
|
||||
|
||||
left_button = new_left_button;
|
||||
right_button = new_right_button;
|
||||
key_down = new_key_down;
|
||||
|
||||
for(std::vector<button>::iterator button_it = buttons.begin();
|
||||
button_it != buttons.end(); ++button_it) {
|
||||
if(button_it->process(mousex,mousey,left_button)) {
|
||||
if(text_widget_text != NULL && use_textbox)
|
||||
*text_widget_text = text_widget.text();
|
||||
|
||||
//if the menu is not used, then return the index of the
|
||||
//button pressed, otherwise return the index of the menu
|
||||
//item selected if the last button is not pressed, and
|
||||
//cancel (-1) otherwise
|
||||
if(menu_.height() == 0) {
|
||||
return button_it - buttons.begin();
|
||||
} else if(buttons.size() <= 1 ||
|
||||
button_it - buttons.begin() != buttons.size()-1) {
|
||||
return menu_.selection();
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SDL_PumpEvents();
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
TITLE_RESULT show_title(display& screen)
|
||||
{
|
||||
SDL_Surface* const title_surface = screen.getImage("title.png",
|
||||
display::UNSCALED);
|
||||
|
||||
if(title_surface == NULL) {
|
||||
std::cerr << "Could not find title image 'title.png'\n";
|
||||
return QUIT_GAME;
|
||||
}
|
||||
|
||||
const int x = screen.x()/2 - title_surface->w/2;
|
||||
const int y = 100;
|
||||
|
||||
screen.blit_surface(x,y,title_surface);
|
||||
|
||||
button tutorial_button(screen,string_table["tutorial_button"]);
|
||||
button new_button(screen,string_table["campaign_button"]);
|
||||
button load_button(screen,string_table["load_button"]);
|
||||
button multi_button(screen,string_table["multiplayer_button"]);
|
||||
button quit_button(screen,string_table["quit_button"]);
|
||||
button language_button(screen,string_table["language_button"]);
|
||||
|
||||
tutorial_button.set_x(700);
|
||||
new_button.set_x(700);
|
||||
load_button.set_x(700);
|
||||
multi_button.set_x(700);
|
||||
quit_button.set_x(700);
|
||||
language_button.set_x(700);
|
||||
|
||||
tutorial_button.set_y(200);
|
||||
new_button.set_y(250);
|
||||
load_button.set_y(300);
|
||||
multi_button.set_y(350);
|
||||
language_button.set_y(400);
|
||||
quit_button.set_y(450);
|
||||
|
||||
bool right_button = true;
|
||||
bool left_button = true;
|
||||
|
||||
tutorial_button.draw();
|
||||
new_button.draw();
|
||||
load_button.draw();
|
||||
multi_button.draw();
|
||||
quit_button.draw();
|
||||
language_button.draw();
|
||||
screen.video().update(0,0,screen.x(),screen.y());
|
||||
|
||||
CKey key;
|
||||
|
||||
for(;;) {
|
||||
int mousex, mousey;
|
||||
const int mouse_flags = SDL_GetMouseState(&mousex,&mousey);
|
||||
|
||||
const bool right_button = mouse_flags&SDL_BUTTON_RMASK;
|
||||
const bool left_button = mouse_flags&SDL_BUTTON_LMASK;
|
||||
|
||||
if(tutorial_button.process(mousex,mousey,left_button))
|
||||
return TUTORIAL;
|
||||
|
||||
if(new_button.process(mousex,mousey,left_button))
|
||||
return NEW_CAMPAIGN;
|
||||
|
||||
if(load_button.process(mousex,mousey,left_button))
|
||||
return LOAD_GAME;
|
||||
|
||||
if(multi_button.process(mousex,mousey,left_button))
|
||||
return MULTIPLAYER;
|
||||
|
||||
if(quit_button.process(mousex,mousey,left_button))
|
||||
return QUIT_GAME;
|
||||
|
||||
if(key[KEY_ESCAPE])
|
||||
return QUIT_GAME;
|
||||
|
||||
if(language_button.process(mousex,mousey,left_button))
|
||||
return CHANGE_LANGUAGE;
|
||||
|
||||
SDL_PumpEvents();
|
||||
|
||||
SDL_Delay(20);
|
||||
}
|
||||
|
||||
return QUIT_GAME;
|
||||
}
|
||||
|
||||
void check_quit(display& gui)
|
||||
{
|
||||
CKey key;
|
||||
if(key[KEY_ESCAPE]) {
|
||||
const int res = gui::show_dialog(gui,NULL,"",
|
||||
string_table["quit_message"],gui::YES_NO);
|
||||
if(res == 0) {
|
||||
throw end_level_exception(QUIT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} //end namespace gui
|
58
src/menu.hpp
Normal file
58
src/menu.hpp
Normal file
|
@ -0,0 +1,58 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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 MENU_HPP_INCLUDED
|
||||
#define MENU_HPP_INCLUDED
|
||||
|
||||
#include "display.hpp"
|
||||
#include "SDL.h"
|
||||
#include "unit.hpp"
|
||||
#include "video.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace gui
|
||||
{
|
||||
|
||||
void draw_dialog_frame(int x, int y, int w, int h, display& disp);
|
||||
|
||||
void draw_rectangle(int x, int y, int w, int h, short colour, SDL_Surface* tg);
|
||||
|
||||
void draw_solid_tinted_rectangle(int x, int y, int w, int h,
|
||||
int r, int g, int b,
|
||||
double alpha, SDL_Surface* target);
|
||||
|
||||
enum DIALOG_TYPE { MESSAGE, OK_ONLY, YES_NO, OK_CANCEL };
|
||||
|
||||
//if a menu is given, then returns -1 if the dialog was cancelled, and the
|
||||
//index of the selection otherwise. If no menu is given, returns the index
|
||||
//of the button that was pressed
|
||||
int show_dialog(display& screen, SDL_Surface* image,
|
||||
const std::string& caption, const std::string& message,
|
||||
DIALOG_TYPE type=MESSAGE,
|
||||
const std::vector<std::string>* menu_items=NULL,
|
||||
const std::vector<unit>* units=NULL,
|
||||
const std::string& text_widget_label="",
|
||||
std::string* text_widget_text=NULL
|
||||
);
|
||||
|
||||
enum TITLE_RESULT { TUTORIAL, NEW_CAMPAIGN, MULTIPLAYER, LOAD_GAME, QUIT_GAME,
|
||||
CHANGE_LANGUAGE };
|
||||
|
||||
TITLE_RESULT show_title(display& screen);
|
||||
|
||||
void check_quit(display& screen);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
104
src/merge_translations.cpp
Normal file
104
src/merge_translations.cpp
Normal file
|
@ -0,0 +1,104 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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.
|
||||
*/
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "config.hpp"
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
if(argc != 3) {
|
||||
std::cerr << "Usage: " << argv[0]
|
||||
<< " translation default-translation\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
const std::string& data1 = read_file(argv[1]);
|
||||
const std::string& data2 = read_file(argv[2]);
|
||||
|
||||
if(data1.empty()) {
|
||||
std::cerr << "Could not read '" << argv[1] << "'\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(data2.empty()) {
|
||||
std::cerr << "Could not read '" << argv[2] << "'\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
config cfg;
|
||||
|
||||
try {
|
||||
cfg.read(data1);
|
||||
} catch(config::error& e) {
|
||||
std::cerr << "error parsing '" << argv[1] << "': " << e.message << "\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::vector<config*> translations = cfg.children["language"];
|
||||
if(translations.empty()) {
|
||||
std::cerr << "no translation data found in '" << argv[1] << "'\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(translations.size() > 1) {
|
||||
std::cerr << "warning: found multiple translations in '" << argv[1]
|
||||
<< "'\n";
|
||||
}
|
||||
|
||||
const std::map<std::string,std::string> strings = translations[0]->values;
|
||||
|
||||
try {
|
||||
cfg.read(data2);
|
||||
} catch(config::error& e) {
|
||||
std::cerr << "error parsing '" << argv[2] << "': " << e.message << "\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
translations = cfg.children["language"];
|
||||
if(translations.empty()) {
|
||||
std::cerr << "no translation data found in '" << argv[2] << "'\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(translations.size() > 1) {
|
||||
std::cerr << "warning: found multiple translations in '" << argv[2]
|
||||
<< "'\n";
|
||||
}
|
||||
|
||||
const std::map<std::string,std::string> default_strings =
|
||||
translations[0]->values;
|
||||
|
||||
std::cout << "[language]\n\n"
|
||||
<< "#strings that were not found in the translation, \n"
|
||||
<< "#and which should be translated now:\n";
|
||||
|
||||
for(std::map<std::string,std::string>::const_iterator i =
|
||||
default_strings.begin(); i != default_strings.end(); ++i) {
|
||||
if(strings.find(i->first) == strings.end()) {
|
||||
std::cout << i->first << "=\"" << i->second << "\"\n";
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "\n#---------------------------------"
|
||||
<< "\n#strings that have already been translated\n";
|
||||
|
||||
for(std::map<std::string,std::string>::const_iterator i = strings.begin();
|
||||
i != strings.end(); ++i) {
|
||||
std::cout << i->first << "=\"" << i->second << "\"\n";
|
||||
}
|
||||
|
||||
std::cout << "[/language]\n";
|
||||
return 0;
|
||||
}
|
122
src/multiplayer.cpp
Normal file
122
src/multiplayer.cpp
Normal file
|
@ -0,0 +1,122 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "language.hpp"
|
||||
#include "menu.hpp"
|
||||
#include "multiplayer.hpp"
|
||||
#include "playlevel.hpp"
|
||||
#include "replay.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
void play_multiplayer(display& disp, game_data& units_data, config& cfg,
|
||||
game_state& state)
|
||||
{
|
||||
std::vector<std::string> options;
|
||||
std::vector<config*>& levels = cfg.children["multiplayer"];
|
||||
for(std::vector<config*>::iterator i = levels.begin(); i!=levels.end();++i){
|
||||
const std::string& lang_name = string_table[(*i)->values["id"]];
|
||||
if(lang_name.empty() == false)
|
||||
options.push_back(lang_name);
|
||||
else
|
||||
options.push_back((*i)->values["name"]);
|
||||
}
|
||||
|
||||
int res = gui::show_dialog(disp,NULL,"",
|
||||
string_table["choose_scenario"],gui::OK_CANCEL,
|
||||
&options);
|
||||
if(res == -1)
|
||||
return;
|
||||
|
||||
config& level = *levels[res];
|
||||
state.label = level.values["name"];
|
||||
|
||||
state.scenario = res;
|
||||
|
||||
std::vector<config*>& sides = level.children["side"];
|
||||
std::vector<config*>& possible_sides = cfg.children["multiplayer_side"];
|
||||
if(sides.empty() || possible_sides.empty()) {
|
||||
std::cerr << "no multiplayer sides found\n";
|
||||
return;
|
||||
}
|
||||
|
||||
for(std::vector<config*>::iterator sd = sides.begin();
|
||||
sd != sides.end(); ++sd) {
|
||||
(*sd)->values["name"] = possible_sides.front()->values["name"];
|
||||
(*sd)->values["type"] = possible_sides.front()->values["type"];
|
||||
(*sd)->values["recruit"] = possible_sides.front()->values["recruit"];
|
||||
}
|
||||
|
||||
res = 0;
|
||||
while(res != sides.size()) {
|
||||
std::vector<std::string> sides_list;
|
||||
for(std::vector<config*>::iterator sd = sides.begin();
|
||||
sd != sides.end(); ++sd) {
|
||||
std::stringstream details;
|
||||
details << (*sd)->values["side"] << ","
|
||||
<< (*sd)->values["name"] << ","
|
||||
<< ((*sd)->values["controller"] == "human" ?
|
||||
string_table["human_controlled"] :
|
||||
string_table["ai_controlled"]);
|
||||
sides_list.push_back(details.str());
|
||||
}
|
||||
|
||||
sides_list.push_back(string_table["start_game"]);
|
||||
|
||||
res = gui::show_dialog(disp,NULL,"",string_table["configure_sides"],
|
||||
gui::MESSAGE,&sides_list);
|
||||
|
||||
if(res >= 0 && res < sides.size()) {
|
||||
std::vector<std::string> choices;
|
||||
|
||||
for(int n = 0; n != 2; ++n) {
|
||||
for(std::vector<config*>::iterator i = possible_sides.begin();
|
||||
i != possible_sides.end(); ++i) {
|
||||
std::stringstream choice;
|
||||
choice << (*i)->values["name"] << " - "
|
||||
<< (n == 0 ? string_table["human_controlled"] :
|
||||
string_table["ai_controlled"]);
|
||||
choices.push_back(choice.str());
|
||||
}
|
||||
}
|
||||
|
||||
int result = gui::show_dialog(disp,NULL,"",
|
||||
string_table["choose_side"],
|
||||
gui::MESSAGE,&choices);
|
||||
if(result >= 0) {
|
||||
sides[res]->values["controller"] = (result >= choices.size()/2)
|
||||
? "ai" : "human";
|
||||
if(result >= choices.size()/2)
|
||||
result -= choices.size()/2;
|
||||
|
||||
assert(result < possible_sides.size());
|
||||
|
||||
std::map<std::string,std::string>& values =
|
||||
possible_sides[result]->values;
|
||||
sides[res]->values["name"] = values["name"];
|
||||
sides[res]->values["type"] = values["type"];
|
||||
sides[res]->values["recruit"] = values["recruit"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state.starting_pos = level;
|
||||
|
||||
recorder.set_save_info(state);
|
||||
|
||||
std::vector<config*> story;
|
||||
play_level(units_data,cfg,&level,disp.video(),state,story);
|
||||
recorder.clear();
|
||||
}
|
25
src/multiplayer.hpp
Normal file
25
src/multiplayer.hpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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 MULTIPLAYER_HPP_INCLUDED
|
||||
#define MULTIPLAYER_HPP_INCLUDED
|
||||
|
||||
#include "config.hpp"
|
||||
#include "display.hpp"
|
||||
#include "gamestatus.hpp"
|
||||
#include "unit_types.hpp"
|
||||
#include "video.hpp"
|
||||
|
||||
void play_multiplayer(display& disp, game_data& units_data,
|
||||
config& cfg, game_state& state);
|
||||
|
||||
#endif
|
238
src/pathfind.cpp
Normal file
238
src/pathfind.cpp
Normal file
|
@ -0,0 +1,238 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "pathfind.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <set>
|
||||
|
||||
namespace {
|
||||
gamemap::location find_vacant(const gamemap& map,
|
||||
const std::map<gamemap::location,unit>& units,
|
||||
const gamemap::location& loc, int depth,
|
||||
gamemap::TERRAIN terrain,
|
||||
std::set<gamemap::location>& touched)
|
||||
{
|
||||
if(touched.count(loc))
|
||||
return gamemap::location();
|
||||
|
||||
touched.insert(loc);
|
||||
|
||||
if(map.on_board(loc) && units.find(loc) == units.end() &&
|
||||
map[loc.x][loc.y] != gamemap::TOWER &&
|
||||
(terrain == 0 || terrain == map[loc.x][loc.y])) {
|
||||
return loc;
|
||||
} else if(depth == 0) {
|
||||
return gamemap::location();
|
||||
} else {
|
||||
gamemap::location adj[6];
|
||||
get_adjacent_tiles(loc,adj);
|
||||
for(int i = 0; i != 6; ++i) {
|
||||
if(!map.on_board(adj[i]) ||
|
||||
terrain != 0 && terrain != map[adj[i].x][adj[i].y])
|
||||
continue;
|
||||
|
||||
const gamemap::location res =
|
||||
find_vacant(map,units,adj[i],depth-1,terrain,touched);
|
||||
|
||||
if(map.on_board(res))
|
||||
return res;
|
||||
}
|
||||
|
||||
return gamemap::location();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
gamemap::location find_vacant_tile(const gamemap& map,
|
||||
const std::map<gamemap::location,unit>& units,
|
||||
const gamemap::location& loc,
|
||||
gamemap::TERRAIN terrain)
|
||||
{
|
||||
for(int i = 1; i != 50; ++i) {
|
||||
std::set<gamemap::location> touch;
|
||||
const gamemap::location res = find_vacant(map,units,loc,i,terrain,touch);
|
||||
if(map.on_board(res))
|
||||
return res;
|
||||
}
|
||||
|
||||
return gamemap::location();
|
||||
}
|
||||
|
||||
void get_adjacent_tiles(const gamemap::location& a, gamemap::location* res)
|
||||
{
|
||||
res->x = a.x;
|
||||
res->y = a.y-1;
|
||||
++res;
|
||||
res->x = a.x+1;
|
||||
res->y = a.y + ((a.x%2) == 0 ? -1:0);
|
||||
++res;
|
||||
res->x = a.x+1;
|
||||
res->y = a.y + ((a.x%2) == 0 ? 0:1);
|
||||
++res;
|
||||
res->x = a.x;
|
||||
res->y = a.y+1;
|
||||
++res;
|
||||
res->x = a.x-1;
|
||||
res->y = a.y + ((a.x%2) == 0 ? 0:1);
|
||||
++res;
|
||||
res->x = a.x-1;
|
||||
res->y = a.y + ((a.x%2) == 0 ? -1:0);
|
||||
}
|
||||
|
||||
bool tiles_adjacent(const gamemap::location& a, const gamemap::location& b)
|
||||
{
|
||||
//two tiles are adjacent if y is different by 1, and x by 0, or if
|
||||
//x is different by 1 and y by 0, or if x and y are each different by 1,
|
||||
//and the x value of the hex with the greater y value is odd
|
||||
|
||||
const int xdiff = abs(a.x - b.x);
|
||||
const int ydiff = abs(a.y - b.y);
|
||||
return ydiff == 1 && a.x == b.x || xdiff == 1 && a.y == b.y ||
|
||||
xdiff == 1 && ydiff == 1 && (a.y > b.y ? (a.x%2) == 1 : (b.x%2) == 1);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
bool enemy_zoc(const gamemap& map,const std::map<gamemap::location,unit>& units,
|
||||
const gamemap::location& loc, const team& current_team, int side)
|
||||
{
|
||||
gamemap::location locs[6];
|
||||
get_adjacent_tiles(loc,locs);
|
||||
for(int i = 0; i != 6; ++i) {
|
||||
const std::map<gamemap::location,unit>::const_iterator it
|
||||
= units.find(locs[i]);
|
||||
if(it != units.end() && it->second.side() != side &&
|
||||
current_team.is_enemy(it->second.side()) &&
|
||||
!it->second.invisible(map.underlying_terrain(
|
||||
map[it->first.x][it->first.y]))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void find_routes(const gamemap& map, const game_data& gamedata,
|
||||
const std::map<gamemap::location,unit>& units,
|
||||
const unit& u,
|
||||
const gamemap::location& loc,
|
||||
int move_left,
|
||||
std::map<gamemap::location,paths::route>& routes,
|
||||
std::vector<team>& teams,
|
||||
bool ignore_zocs, bool allow_teleport)
|
||||
{
|
||||
//find adjacent tiles
|
||||
std::vector<gamemap::location> locs(6);
|
||||
get_adjacent_tiles(loc,&locs[0]);
|
||||
|
||||
//check for teleporting units
|
||||
if(allow_teleport && map[loc.x][loc.y] == gamemap::TOWER) {
|
||||
const std::vector<gamemap::location>& towers = map.towers();
|
||||
|
||||
//if we are on a tower, see all friendly towers that we can
|
||||
//teleport to
|
||||
for(std::vector<gamemap::location>::const_iterator t = towers.begin();
|
||||
t != towers.end(); ++t) {
|
||||
if(!teams[u.side()-1].owns_tower(*t) ||
|
||||
units.find(*t) != units.end())
|
||||
continue;
|
||||
|
||||
locs.push_back(*t);
|
||||
}
|
||||
}
|
||||
|
||||
//iterate over all adjacent tiles
|
||||
for(int i = 0; i != locs.size(); ++i) {
|
||||
const gamemap::location& currentloc = locs[i];
|
||||
|
||||
//check if the adjacent location is off the board
|
||||
if(currentloc.x < 0 || currentloc.y < 0 ||
|
||||
currentloc.x >= map.x() || currentloc.y >= map.y())
|
||||
continue;
|
||||
|
||||
//see if the tile is on top of an enemy unit
|
||||
const std::map<gamemap::location,unit>::const_iterator unit_it =
|
||||
units.find(locs[i]);
|
||||
if(unit_it != units.end() && unit_it->second.side() != u.side())
|
||||
continue;
|
||||
|
||||
//find the terrain of the adjacent location
|
||||
const gamemap::TERRAIN terrain = map[currentloc.x][currentloc.y];
|
||||
|
||||
//find the movement cost of this type onto the terrain
|
||||
const int move_cost = u.movement_cost(map,terrain);
|
||||
if(move_cost <= move_left) {
|
||||
const std::map<gamemap::location,paths::route>::const_iterator
|
||||
rtit = routes.find(currentloc);
|
||||
|
||||
//if a better route to that tile has already been found
|
||||
if(rtit != routes.end() &&
|
||||
rtit->second.move_left >= move_left - move_cost)
|
||||
continue;
|
||||
|
||||
const bool zoc = enemy_zoc(map,units,currentloc,
|
||||
teams[u.side()-1],u.side()) &&
|
||||
!ignore_zocs;
|
||||
paths::route new_route = routes[loc];
|
||||
new_route.steps.push_back(loc);
|
||||
new_route.move_left = zoc ? 0 : move_left - move_cost;
|
||||
routes[currentloc] = new_route;
|
||||
|
||||
if(new_route.move_left > 0) {
|
||||
find_routes(map,gamedata,units,u,currentloc,
|
||||
new_route.move_left,routes,teams,ignore_zocs,
|
||||
allow_teleport);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} //end anon namespace
|
||||
|
||||
paths::paths(const gamemap& map, const game_data& gamedata,
|
||||
const std::map<gamemap::location,unit>& units,
|
||||
const gamemap::location& loc,
|
||||
std::vector<team>& teams,
|
||||
bool ignore_zocs, bool allow_teleport)
|
||||
{
|
||||
const std::map<gamemap::location,unit>::const_iterator i = units.find(loc);
|
||||
if(i == units.end()) {
|
||||
std::cerr << "unit not found\n";
|
||||
return;
|
||||
}
|
||||
|
||||
routes[loc].move_left = i->second.movement_left();
|
||||
find_routes(map,gamedata,units,i->second,loc,
|
||||
i->second.movement_left(),routes,teams,
|
||||
ignore_zocs,allow_teleport);
|
||||
|
||||
if(i->second.can_attack()) {
|
||||
gamemap::location adjacent[6];
|
||||
get_adjacent_tiles(loc,adjacent);
|
||||
for(int j = 0; j != 6; ++j) {
|
||||
const std::map<gamemap::location,unit>::const_iterator enemy =
|
||||
units.find(adjacent[j]);
|
||||
if(enemy != units.end() &&
|
||||
enemy->second.side() != i->second.side() &&
|
||||
teams[i->second.side()-1].is_enemy(enemy->second.side())) {
|
||||
route new_route;
|
||||
new_route.move_left = -1;
|
||||
routes.insert(std::pair<gamemap::location,route>(
|
||||
adjacent[j],new_route));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
153
src/pathfind.hpp
Normal file
153
src/pathfind.hpp
Normal file
|
@ -0,0 +1,153 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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 PATHFIND_H_INCLUDED
|
||||
#define PATHFIND_H_INCLUDED
|
||||
|
||||
#include "map.hpp"
|
||||
#include "unit.hpp"
|
||||
#include "unit_types.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
|
||||
void get_adjacent_tiles(const gamemap::location& a, gamemap::location* res);
|
||||
bool tiles_adjacent(const gamemap::location& a, const gamemap::location& b);
|
||||
|
||||
gamemap::location find_vacant_tile(const gamemap& map,
|
||||
const std::map<gamemap::location,unit>& un,
|
||||
const gamemap::location& loc,
|
||||
gamemap::TERRAIN terrain=0);
|
||||
|
||||
struct paths
|
||||
{
|
||||
paths() {}
|
||||
paths(const gamemap& map, const game_data& gamedata,
|
||||
const std::map<gamemap::location,unit>& units,
|
||||
const gamemap::location& loc, std::vector<team>& teams,
|
||||
bool ignore_zocs, bool allow_teleport);
|
||||
|
||||
struct route
|
||||
{
|
||||
route() : move_left(0) {}
|
||||
std::vector<gamemap::location> steps;
|
||||
int move_left;
|
||||
};
|
||||
|
||||
std::map<gamemap::location,route> routes;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
struct node {
|
||||
node(const gamemap::location& pos, const gamemap::location& dst,
|
||||
double cost, node* parent)
|
||||
: parent(parent), loc(pos), g(cost),
|
||||
h(sqrt(pow(abs(dst.x-pos.x),2) + pow(abs(dst.y-pos.y),2)))
|
||||
{
|
||||
f = g + h;
|
||||
}
|
||||
|
||||
node* parent;
|
||||
gamemap::location loc;
|
||||
double g, h, f;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
paths::route a_star_search(const gamemap::location& src,
|
||||
const gamemap::location& dst, double stop_at, T obj)
|
||||
{
|
||||
std::cout << "a* search: " << src.x << ", " << src.y << " - " << dst.x << ", " << dst.y << "\n";
|
||||
using namespace detail;
|
||||
typedef gamemap::location location;
|
||||
std::list<node> open_list, closed_list;
|
||||
|
||||
open_list.push_back(node(src,dst,0.0,NULL));
|
||||
|
||||
while(!open_list.empty()) {
|
||||
|
||||
//find the lowest estimated cost node on the open list
|
||||
std::list<node>::iterator lowest = open_list.end(), i;
|
||||
for(i = open_list.begin(); i != open_list.end(); ++i) {
|
||||
if(lowest == open_list.end() || i->f < lowest->f) {
|
||||
lowest = i;
|
||||
}
|
||||
}
|
||||
|
||||
if(lowest->f > stop_at) {
|
||||
break;
|
||||
}
|
||||
|
||||
//std::cerr << "processing " << (lowest->loc.x+1) << "," << (lowest->loc.y+1) << " with cost = " << lowest->g << " (known) + " << lowest->h << " (estimated) = " << lowest->f << "\n";
|
||||
|
||||
//move the lowest element from the open list to the closed list
|
||||
closed_list.splice(closed_list.begin(),open_list,lowest);
|
||||
|
||||
//find nodes we can go to from this node
|
||||
location locs[6];
|
||||
get_adjacent_tiles(lowest->loc,locs);
|
||||
for(int j = 0; j != 6; ++j) {
|
||||
|
||||
//if we have found a solution
|
||||
if(locs[j] == dst) {
|
||||
paths::route rt;
|
||||
for(node* n = &*lowest; n != NULL; n = n->parent) {
|
||||
rt.steps.push_back(n->loc);
|
||||
}
|
||||
|
||||
std::reverse(rt.steps.begin(),rt.steps.end());
|
||||
rt.steps.push_back(dst);
|
||||
rt.move_left = int(lowest->f);
|
||||
|
||||
std::cout << "exiting a* search (solved)\n";
|
||||
|
||||
return rt;
|
||||
}
|
||||
|
||||
const node nd(locs[j],dst,lowest->g+obj.cost(locs[j]),&*lowest);
|
||||
|
||||
for(i = open_list.begin(); i != open_list.end(); ++i) {
|
||||
if(i->loc == nd.loc && i->f <= nd.f) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(i != open_list.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for(i = closed_list.begin(); i != closed_list.end(); ++i) {
|
||||
if(i != lowest && i->loc == nd.loc && i->f <= nd.f) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(i != closed_list.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
open_list.push_back(nd);
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "aborted a* search\n";
|
||||
paths::route val;
|
||||
val.move_left = 100000;
|
||||
return val;
|
||||
}
|
||||
|
||||
#endif
|
293
src/playlevel.cpp
Normal file
293
src/playlevel.cpp
Normal file
|
@ -0,0 +1,293 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "game_events.hpp"
|
||||
#include "intro.hpp"
|
||||
#include "language.hpp"
|
||||
#include "playlevel.hpp"
|
||||
#include "playturn.hpp"
|
||||
#include "preferences.hpp"
|
||||
#include "replay.hpp"
|
||||
#include "sound.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
LEVEL_RESULT play_level(game_data& gameinfo, config& terrain_config,
|
||||
config* level, CVideo& video,
|
||||
game_state& state_of_game,
|
||||
std::vector<config*>& story)
|
||||
{
|
||||
const int num_turns = atoi(level->values["turns"].c_str());
|
||||
gamestatus status(num_turns);
|
||||
|
||||
gamemap map(terrain_config,read_file("data/maps/" + level->values["map"]));
|
||||
|
||||
CKey key;
|
||||
typedef std::map<gamemap::location,unit> units_map;
|
||||
units_map units;
|
||||
|
||||
std::vector<team> teams;
|
||||
|
||||
std::vector<config*>& unit_cfg = level->children["side"];
|
||||
for(std::vector<config*>::iterator ui = unit_cfg.begin();
|
||||
ui != unit_cfg.end(); ++ui) {
|
||||
unit new_unit(gameinfo, **ui);
|
||||
if(ui == unit_cfg.begin()) {
|
||||
for(std::vector<unit>::iterator it =
|
||||
state_of_game.available_units.begin();
|
||||
it != state_of_game.available_units.end(); ++it) {
|
||||
if(it->can_recruit()) {
|
||||
new_unit = *it;
|
||||
state_of_game.available_units.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string gold = (*ui)->values["gold"];
|
||||
if(gold.empty())
|
||||
gold = "100";
|
||||
|
||||
int ngold = ::atoi(gold.c_str());
|
||||
if(ui == unit_cfg.begin() && state_of_game.gold >= 0)
|
||||
ngold = state_of_game.gold;
|
||||
|
||||
units.insert(std::pair<gamemap::location,unit>(
|
||||
map.starting_position(new_unit.side()), new_unit));
|
||||
teams.push_back(team(**ui,ngold));
|
||||
|
||||
//if there are additional starting units on this side
|
||||
std::vector<config*>& starting_units = (*ui)->children["unit"];
|
||||
for(std::vector<config*>::iterator su = starting_units.begin();
|
||||
su != starting_units.end(); ++su) {
|
||||
unit new_unit(gameinfo,**su);
|
||||
const std::string& x = (*su)->values["x"];
|
||||
const std::string& y = (*su)->values["y"];
|
||||
|
||||
const gamemap::location loc(**su);
|
||||
if(x.size() == 0 || y.size() == 0 || !map.on_board(loc)) {
|
||||
state_of_game.available_units.push_back(new_unit);
|
||||
} else {
|
||||
units.insert(std::pair<gamemap::location,unit>(loc,new_unit));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
display gui(units,video,map,status,teams);
|
||||
const preferences::display_manager prefs_disp_manager(&gui);
|
||||
|
||||
if(recorder.skipping() == false) {
|
||||
for(std::vector<config*>::iterator story_i = story.begin();
|
||||
story_i != story.end(); ++story_i) {
|
||||
show_intro(gui,**story_i);
|
||||
}
|
||||
|
||||
show_map_scene(gui,*level);
|
||||
}
|
||||
|
||||
const std::string& music = level->values["music"];
|
||||
if(!music.empty()) {
|
||||
sound::play_music(music);
|
||||
}
|
||||
|
||||
game_events::manager events_manager(*level,gui,map,units,
|
||||
state_of_game,gameinfo);
|
||||
|
||||
//find a list of 'items' (i.e. overlays) on the level, and add them
|
||||
std::vector<config*>& overlays = level->children["item"];
|
||||
for(std::vector<config*>::iterator overlay = overlays.begin();
|
||||
overlay != overlays.end(); ++overlay) {
|
||||
gui.add_overlay(gamemap::location(**overlay),
|
||||
(*overlay)->values["image"]);
|
||||
}
|
||||
|
||||
const double scroll_speed = 30.0;
|
||||
const double zoom_amount = 5.0;
|
||||
|
||||
for(units_map::iterator i = units.begin(); i != units.end(); ++i) {
|
||||
i->second.new_turn();
|
||||
}
|
||||
|
||||
bool left_button = false, right_button = false;
|
||||
|
||||
gamemap::location selected_hex;
|
||||
|
||||
gui.scroll_to_tile(map.starting_position(1).x,map.starting_position(1).y,
|
||||
display::WARP);
|
||||
|
||||
bool replaying = (recorder.empty() == false);
|
||||
|
||||
std::cout << "starting main loop\n";
|
||||
for(bool first_time = true; true; first_time = false) {
|
||||
try {
|
||||
|
||||
if(first_time) {
|
||||
update_locker lock_display(gui,recorder.skipping());
|
||||
game_events::fire("start");
|
||||
}
|
||||
|
||||
gui.invalidate_game_status();
|
||||
|
||||
for(std::vector<team>::iterator team_it = teams.begin();
|
||||
team_it != teams.end(); ++team_it) {
|
||||
const int player_number = (team_it - teams.begin()) + 1;
|
||||
|
||||
calculate_healing(gui,map,units,player_number);
|
||||
|
||||
//scroll the map to the leader
|
||||
const units_map::iterator leader =
|
||||
find_leader(units,player_number);
|
||||
|
||||
if(leader != units.end() && !recorder.skipping()) {
|
||||
gui.scroll_to_tile(leader->first.x,leader->first.y);
|
||||
}
|
||||
|
||||
if(replaying) {
|
||||
replaying = do_replay(gui,map,gameinfo,units,teams,
|
||||
player_number,status,state_of_game);
|
||||
}
|
||||
|
||||
if(!replaying && team_it->is_human()) {
|
||||
play_turn(gameinfo,state_of_game,status,terrain_config,
|
||||
level, video, key, gui, events_manager, map,
|
||||
teams, player_number, units);
|
||||
|
||||
if(game_config::debug)
|
||||
display::clear_debug_highlights();
|
||||
|
||||
} else if(!replaying) {
|
||||
ai::do_move(gui,map,gameinfo,units,teams,
|
||||
player_number,status);
|
||||
|
||||
gui.invalidate_unit();
|
||||
gui.invalidate_game_status();
|
||||
gui.invalidate_all();
|
||||
gui.draw();
|
||||
SDL_Delay(1000);
|
||||
}
|
||||
|
||||
for(units_map::iterator uit = units.begin();
|
||||
uit != units.end(); ++uit) {
|
||||
if(uit->second.side() == player_number)
|
||||
uit->second.end_turn();
|
||||
}
|
||||
|
||||
game_events::pump();
|
||||
|
||||
const int victory = check_victory(units);
|
||||
if(victory > 1) {
|
||||
throw end_level_exception(DEFEAT);
|
||||
} else if(victory == 1) {
|
||||
throw end_level_exception(VICTORY);
|
||||
}
|
||||
}
|
||||
|
||||
//time has run out
|
||||
if(!status.next_turn()) {
|
||||
game_events::fire("time over");
|
||||
throw end_level_exception(DEFEAT);
|
||||
}
|
||||
|
||||
std::stringstream event_stream;
|
||||
event_stream << "turn " << status.turn();
|
||||
|
||||
{
|
||||
update_locker lock_display(gui,recorder.skipping());
|
||||
game_events::fire(event_stream.str());
|
||||
}
|
||||
|
||||
std::map<int,int> expenditure;
|
||||
for(units_map::iterator i = units.begin();
|
||||
i != units.end(); ++i) {
|
||||
i->second.new_turn();
|
||||
expenditure[i->second.side()]++;
|
||||
}
|
||||
|
||||
int team_num = 1;
|
||||
for(std::vector<team>::iterator it = teams.begin();
|
||||
it != teams.end(); ++it, ++team_num) {
|
||||
it->new_turn();
|
||||
it->spend_gold(expenditure[team_num]);
|
||||
}
|
||||
} catch(end_level_exception& end_level) {
|
||||
|
||||
if(end_level.result == QUIT || end_level.result == REPLAY) {
|
||||
return end_level.result;
|
||||
} else if(end_level.result == DEFEAT) {
|
||||
try {
|
||||
game_events::fire("defeat");
|
||||
} catch(end_level_exception&) {
|
||||
}
|
||||
|
||||
gui::show_dialog(gui,NULL,
|
||||
string_table["defeat_heading"],
|
||||
string_table["defeat_message"],
|
||||
gui::OK_ONLY);
|
||||
return DEFEAT;
|
||||
} else if(end_level.result == VICTORY) {
|
||||
try {
|
||||
game_events::fire("victory");
|
||||
} catch(end_level_exception&) {
|
||||
}
|
||||
|
||||
//add all the units that survived the scenario
|
||||
for(std::map<gamemap::location,unit>::iterator un =
|
||||
units.begin(); un != units.end(); ++un) {
|
||||
if(un->second.side() == 1) {
|
||||
un->second.new_level();
|
||||
state_of_game.available_units.
|
||||
push_back(un->second);
|
||||
}
|
||||
}
|
||||
|
||||
const int remaining_gold = teams[0].gold();
|
||||
const int finishing_bonus_per_turn =
|
||||
map.towers().size()*game_config::tower_income;
|
||||
const int turns_left = status.number_of_turns() - status.turn();
|
||||
const int finishing_bonus = end_level.gold_bonus ?
|
||||
(finishing_bonus_per_turn * turns_left) : 0;
|
||||
state_of_game.gold = (remaining_gold+finishing_bonus)/2;
|
||||
|
||||
gui::show_dialog(gui,NULL,string_table["victory_heading"],
|
||||
string_table["victory_message"],gui::OK_ONLY);
|
||||
std::stringstream report;
|
||||
report << string_table["remaining_gold"] << ": "
|
||||
<< remaining_gold << "\n";
|
||||
if(end_level.gold_bonus) {
|
||||
report << string_table["early_finish_bonus"] << ": "
|
||||
<< finishing_bonus_per_turn
|
||||
<< " " << string_table["per_turn"] << "\n"
|
||||
<< string_table["turns_finished_early"] << ": "
|
||||
<< string_table["bonus"] << ": "
|
||||
<< finishing_bonus << "\n"
|
||||
<< string_table["gold"] << ": "
|
||||
<< (remaining_gold+finishing_bonus);
|
||||
}
|
||||
|
||||
report << "\n" << string_table["fifty_percent"] << "\n"
|
||||
<< string_table["retained_gold"] << ": "
|
||||
<< state_of_game.gold;
|
||||
|
||||
gui::show_dialog(gui,NULL,"",report.str(),gui::OK_ONLY);
|
||||
return VICTORY;
|
||||
}
|
||||
} //end catch
|
||||
catch(replay::error& e) {
|
||||
gui::show_dialog(gui,NULL,"","The file you loaded is corrupt "
|
||||
"or from a different version of the game",gui::OK_ONLY);
|
||||
return DEFEAT;
|
||||
}
|
||||
|
||||
} //end for(;;)
|
||||
|
||||
return QUIT;
|
||||
}
|
52
src/playlevel.hpp
Normal file
52
src/playlevel.hpp
Normal file
|
@ -0,0 +1,52 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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 PLAY_LEVEL_HPP_INCLUDED
|
||||
#define PLAY_LEVEL_HPP_INCLUDED
|
||||
|
||||
#include "actions.hpp"
|
||||
#include "ai.hpp"
|
||||
#include "config.hpp"
|
||||
#include "dialogs.hpp"
|
||||
#include "display.hpp"
|
||||
#include "game_config.hpp"
|
||||
#include "gamestatus.hpp"
|
||||
#include "key.hpp"
|
||||
#include "menu.hpp"
|
||||
#include "pathfind.hpp"
|
||||
#include "team.hpp"
|
||||
#include "unit_types.hpp"
|
||||
#include "unit.hpp"
|
||||
#include "video.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
enum LEVEL_RESULT { VICTORY, DEFEAT, QUIT, REPLAY };
|
||||
|
||||
struct end_level_exception {
|
||||
end_level_exception(LEVEL_RESULT res, bool bonus=true)
|
||||
: result(res), gold_bonus(bonus)
|
||||
{}
|
||||
LEVEL_RESULT result;
|
||||
bool gold_bonus;
|
||||
};
|
||||
|
||||
LEVEL_RESULT play_level(game_data& gameinfo, config& terrain_config,
|
||||
config* level, CVideo& video,
|
||||
game_state& state_of_game,
|
||||
std::vector<config*>& story);
|
||||
|
||||
#endif
|
838
src/playturn.cpp
Normal file
838
src/playturn.cpp
Normal file
|
@ -0,0 +1,838 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "hotkeys.hpp"
|
||||
#include "language.hpp"
|
||||
#include "playlevel.hpp"
|
||||
#include "playturn.hpp"
|
||||
#include "preferences.hpp"
|
||||
#include "replay.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <deque>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
struct undo_action {
|
||||
undo_action(const std::vector<gamemap::location>& rt,int sm,int orig=-1)
|
||||
: route(rt), starting_moves(sm), original_village_owner(orig) {}
|
||||
std::vector<gamemap::location> route;
|
||||
int starting_moves;
|
||||
int original_village_owner;
|
||||
};
|
||||
|
||||
void play_turn(game_data& gameinfo, game_state& state_of_game,
|
||||
gamestatus& status, config& terrain_config, config* level,
|
||||
CVideo& video, CKey& key, display& gui,
|
||||
game_events::manager& events_manager, gamemap& map,
|
||||
std::vector<team>& teams, int team_num,
|
||||
std::map<gamemap::location,unit>& units)
|
||||
{
|
||||
gui.set_team(team_num-1);
|
||||
gui.invalidate_all();
|
||||
gui.draw();
|
||||
gui.update_display();
|
||||
|
||||
team& current_team = teams[team_num-1];
|
||||
|
||||
const double scroll_speed = 30.0;
|
||||
const double zoom_amount = 5.0;
|
||||
|
||||
const std::string menu_items[] = {"scenario_objectives","recruit",
|
||||
"recall","unit_list","save_game",
|
||||
"preferences","end_turn"};
|
||||
std::vector<std::string> menu;
|
||||
for(const std::string* menu_items_ptr = menu_items;
|
||||
menu_items_ptr != menu_items + sizeof(menu_items)/sizeof(*menu_items);
|
||||
++menu_items_ptr) {
|
||||
menu.push_back(string_table[*menu_items_ptr]);
|
||||
}
|
||||
|
||||
typedef std::map<gamemap::location,unit> units_map;
|
||||
|
||||
gamemap::location next_unit;
|
||||
|
||||
bool left_button = false, right_button = false;
|
||||
|
||||
const paths_wiper wiper(gui);
|
||||
paths current_paths;
|
||||
bool enemy_paths = false;
|
||||
|
||||
gamemap::location last_hex;
|
||||
gamemap::location selected_hex;
|
||||
|
||||
std::deque<undo_action> undo_stack, redo_stack;
|
||||
|
||||
for(;;) {
|
||||
int mousex, mousey;
|
||||
const int mouse_flags = SDL_GetMouseState(&mousex,&mousey);
|
||||
const bool new_left_button = mouse_flags & SDL_BUTTON_LMASK;
|
||||
const bool new_right_button = mouse_flags & SDL_BUTTON_RMASK;
|
||||
|
||||
gamemap::location new_hex = gui.hex_clicked_on(mousex,mousey);
|
||||
|
||||
//highlight the hex that is currently moused over
|
||||
if(new_hex != last_hex) {
|
||||
gui.highlight_hex(new_hex);
|
||||
|
||||
if(enemy_paths && new_hex != last_hex) {
|
||||
gui.set_paths(NULL);
|
||||
current_paths = paths();
|
||||
enemy_paths = false;
|
||||
}
|
||||
}
|
||||
|
||||
HOTKEY_COMMAND command = HOTKEY_NULL;
|
||||
|
||||
if(new_left_button) {
|
||||
const gamemap::location loc =gui.minimap_location_on(mousex,mousey);
|
||||
if(loc.valid()) {
|
||||
gui.scroll_to_tile(loc.x,loc.y,display::WARP);
|
||||
}
|
||||
} else if(new_hex != last_hex && current_paths.routes.empty()) {
|
||||
const units_map::iterator u = units.find(new_hex);
|
||||
if(u != units.end() && u->second.side() != team_num) {
|
||||
const bool ignore_zocs = u->second.type().is_skirmisher();
|
||||
const bool teleport = u->second.type().teleports();
|
||||
current_paths = paths(map,gameinfo,units,new_hex,teams,
|
||||
ignore_zocs,teleport);
|
||||
gui.set_paths(¤t_paths);
|
||||
enemy_paths = true;
|
||||
}
|
||||
}
|
||||
|
||||
last_hex = new_hex;
|
||||
|
||||
if(!left_button && new_left_button) {
|
||||
const gamemap::location& hex = gui.hex_clicked_on(mousex,mousey);
|
||||
|
||||
units_map::iterator u = units.find(selected_hex);
|
||||
|
||||
//if we can move to that tile
|
||||
const std::map<gamemap::location,paths::route>::const_iterator
|
||||
route = enemy_paths ? current_paths.routes.end() :
|
||||
current_paths.routes.find(hex);
|
||||
units_map::iterator enemy = units.find(hex);
|
||||
|
||||
//see if we're trying to attack an enemy
|
||||
if(route != current_paths.routes.end() && enemy != units.end() &&
|
||||
hex != selected_hex &&
|
||||
enemy->second.side() != u->second.side()) {
|
||||
|
||||
const unit_type& type = u->second.type();
|
||||
const unit_type& enemy_type = enemy->second.type();
|
||||
const std::vector<attack_type>& attacks = u->second.attacks();
|
||||
std::vector<std::string> items;
|
||||
const std::vector<attack_type>& defends =
|
||||
enemy->second.attacks();
|
||||
|
||||
std::vector<unit> units_list;
|
||||
|
||||
for(int a = 0; a != attacks.size(); ++a) {
|
||||
const battle_stats stats = evaluate_battle_stats(
|
||||
map,selected_hex,hex,
|
||||
a,units,status,gameinfo);
|
||||
|
||||
const std::string& lang_attack_name =
|
||||
string_table["weapon_name_"+stats.attack_name];
|
||||
const std::string& lang_attack_type =
|
||||
string_table["weapon_type_"+stats.attack_type];
|
||||
const std::string& lang_range =
|
||||
string_table[stats.range == "Melee" ?
|
||||
"short_range" : "long_range"];
|
||||
|
||||
const std::string& lang_defend_name =
|
||||
string_table["weapon_name_"+stats.defend_name];
|
||||
const std::string& lang_defend_type =
|
||||
string_table["weapon_type_"+stats.defend_type];
|
||||
|
||||
const std::string& attack_name = lang_attack_name.empty() ?
|
||||
stats.attack_name : lang_attack_name;
|
||||
const std::string& attack_type = lang_attack_type.empty() ?
|
||||
stats.attack_type : lang_attack_type;
|
||||
const std::string& defend_name = lang_defend_name.empty() ?
|
||||
stats.defend_name : lang_defend_name;
|
||||
const std::string& defend_type = lang_defend_type.empty() ?
|
||||
stats.defend_type : lang_defend_type;
|
||||
|
||||
const std::string& range = lang_range.empty() ?
|
||||
stats.range : lang_range;
|
||||
|
||||
std::stringstream att;
|
||||
att << attack_name << " (" << attack_type
|
||||
<< ") " << stats.damage_defender_takes << "-"
|
||||
<< stats.nattacks << " " << range << " "
|
||||
<< int(ceil(100.0*stats.chance_to_hit_defender)) << "%";
|
||||
|
||||
att << "," << string_table["versus"] << ",";
|
||||
att << defend_name << " (" << defend_type
|
||||
<< ") " << stats.damage_attacker_takes << "-"
|
||||
<< stats.ndefends << " "
|
||||
<< int(ceil(100.0*stats.chance_to_hit_attacker)) << "%";
|
||||
|
||||
items.push_back(att.str());
|
||||
units_list.push_back(enemy->second);
|
||||
}
|
||||
|
||||
//make it so that when we attack an enemy, the attacking unit
|
||||
//is again shown in the status bar, so that we can easily
|
||||
//compare between the attacking and defending unit
|
||||
gui.highlight_hex(gamemap::location());
|
||||
gui.draw(true,true);
|
||||
|
||||
const int res = gui::show_dialog(gui,NULL,"",
|
||||
string_table["choose_weapon"]+":\n",
|
||||
gui::OK_CANCEL,&items,&units_list);
|
||||
|
||||
if(res >= 0 && res < attacks.size()) {
|
||||
undo_stack.clear();
|
||||
redo_stack.clear();
|
||||
|
||||
current_paths = paths();
|
||||
gui.set_paths(NULL);
|
||||
|
||||
game_events::fire("attack",selected_hex,hex);
|
||||
|
||||
//the event could have killed either the attacker or
|
||||
//defender, so we have to make sure they still exist
|
||||
u = units.find(selected_hex);
|
||||
enemy = units.find(hex);
|
||||
|
||||
if(u == units.end() || enemy == units.end() ||
|
||||
res >= u->second.attacks().size())
|
||||
continue;
|
||||
|
||||
gui.invalidate_all();
|
||||
gui.draw();
|
||||
|
||||
recorder.add_attack(selected_hex,hex,res);
|
||||
|
||||
attack(gui,map,selected_hex,hex,res,units,
|
||||
status,gameinfo,true);
|
||||
|
||||
dialogs::advance_unit(gameinfo,units,selected_hex,gui);
|
||||
dialogs::advance_unit(gameinfo,units,hex,gui);
|
||||
|
||||
selected_hex = gamemap::location();
|
||||
|
||||
gui.invalidate_unit();
|
||||
gui.draw(); //clear the screen
|
||||
|
||||
const int winner = check_victory(units);
|
||||
|
||||
//if the player has won
|
||||
if(winner == team_num) {
|
||||
throw end_level_exception(VICTORY);
|
||||
} else if(winner >= 1) {
|
||||
throw end_level_exception(DEFEAT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//otherwise we're just trying to move to a hex
|
||||
else if(selected_hex.valid() && selected_hex != hex &&
|
||||
enemy == units.end() &&
|
||||
route != current_paths.routes.end()) {
|
||||
std::vector<gamemap::location> steps = route->second.steps;
|
||||
steps.push_back(hex);
|
||||
|
||||
//if an event mutates the game environment, then this
|
||||
//move must be marked un-redoable
|
||||
bool event_mutated = false;
|
||||
|
||||
int orig_tower_owner = -1;
|
||||
int starting_moves = 0;
|
||||
int unit_side = 0;
|
||||
if(u != units.end()) {
|
||||
unit un = u->second;
|
||||
starting_moves = un.movement_left();
|
||||
unit_side = un.side();
|
||||
units.erase(u);
|
||||
gui.move_unit(steps,un);
|
||||
recorder.add_movement(selected_hex,hex);
|
||||
un.set_movement(route->second.move_left);
|
||||
|
||||
//see if the unit has gotten a tower
|
||||
if(map[hex.x][hex.y] == gamemap::TOWER) {
|
||||
orig_tower_owner = tower_owner(hex,teams);
|
||||
get_tower(hex,teams,team_num-1);
|
||||
un.set_movement(0); //end of turn if you get a tower
|
||||
}
|
||||
|
||||
units.insert(std::pair<gamemap::location,unit>(hex,un));
|
||||
gui.invalidate_unit();
|
||||
|
||||
//fire the move to event. If the event mutates
|
||||
//the game state, then the user cannot undo the move
|
||||
event_mutated = game_events::fire("moveto",hex);
|
||||
} else {
|
||||
assert(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
selected_hex = gamemap::location();
|
||||
gui.select_hex(gamemap::location());
|
||||
gui.set_paths(NULL);
|
||||
current_paths = paths();
|
||||
|
||||
//if there is an enemy in a surrounding hex, then
|
||||
//highlight attack options
|
||||
gamemap::location adj[6];
|
||||
get_adjacent_tiles(steps.back(),adj);
|
||||
|
||||
int n;
|
||||
for(n = 0; n != 6; ++n) {
|
||||
const units_map::const_iterator u_it = units.find(adj[n]);
|
||||
if(u_it != units.end() && u_it->second.side() != unit_side
|
||||
&& current_team.is_enemy(u_it->second.side())){
|
||||
current_paths.routes[adj[n]] = paths::route();
|
||||
}
|
||||
}
|
||||
|
||||
if(current_paths.routes.empty() == false) {
|
||||
current_paths.routes[steps.back()] = paths::route();
|
||||
selected_hex = steps.back();
|
||||
gui.select_hex(steps.back());
|
||||
gui.set_paths(¤t_paths);
|
||||
}
|
||||
|
||||
undo_stack.push_back(undo_action(steps,starting_moves,
|
||||
orig_tower_owner));
|
||||
redo_stack.clear();
|
||||
|
||||
if(event_mutated) {
|
||||
undo_stack.clear();
|
||||
}
|
||||
|
||||
} else {
|
||||
gui.set_paths(NULL);
|
||||
current_paths = paths();
|
||||
|
||||
selected_hex = hex;
|
||||
gui.select_hex(hex);
|
||||
|
||||
const units_map::iterator it = units.find(hex);
|
||||
if(it != units.end() && it->second.side() == team_num) {
|
||||
const bool ignore_zocs = it->second.type().is_skirmisher();
|
||||
const bool teleport = it->second.type().teleports();
|
||||
current_paths = paths(map,gameinfo,units,hex,teams,
|
||||
ignore_zocs,teleport);
|
||||
gui.set_paths(¤t_paths);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
left_button = new_left_button;
|
||||
|
||||
if(!right_button && new_right_button) {
|
||||
if(!current_paths.routes.empty()) {
|
||||
selected_hex = gamemap::location();
|
||||
gui.select_hex(gamemap::location());
|
||||
gui.set_paths(NULL);
|
||||
current_paths = paths();
|
||||
} else {
|
||||
const units_map::const_iterator un = units.find(
|
||||
gui.hex_clicked_on(mousex,mousey));
|
||||
if(un != units.end()) {
|
||||
menu.push_back(string_table["describe_unit"]);
|
||||
}
|
||||
|
||||
const int res = gui::show_dialog(gui,NULL,"",
|
||||
string_table["options"]+":\n",
|
||||
gui::MESSAGE,&menu);
|
||||
|
||||
const std::string result = res != -1 ? menu[res] : "";
|
||||
|
||||
if(un != units.end()) {
|
||||
menu.pop_back();
|
||||
}
|
||||
|
||||
if(result == string_table["describe_unit"]) {
|
||||
|
||||
const std::string description
|
||||
= un->second.type().unit_description() +
|
||||
"\n\n" + string_table["see_also"];
|
||||
|
||||
std::vector<std::string> options;
|
||||
|
||||
options.push_back(string_table["terrain_info"]);
|
||||
options.push_back(string_table["attack_resistance"]);
|
||||
options.push_back(string_table["close_window"]);
|
||||
|
||||
std::vector<unit> units_list(options.size(),un->second);
|
||||
|
||||
SDL_Surface* const unit_image =
|
||||
gui.getImage(un->second.type().image_profile(),
|
||||
display::UNSCALED);
|
||||
|
||||
const int res = gui::show_dialog(gui,unit_image,
|
||||
un->second.type().language_name(),
|
||||
description,gui::MESSAGE,&options,&units_list);
|
||||
|
||||
//terrain table
|
||||
if(res == 0) {
|
||||
command = HOTKEY_TERRAIN_TABLE;
|
||||
}
|
||||
|
||||
//attack resistance table
|
||||
else if(res == 1) {
|
||||
command = HOTKEY_ATTACK_RESISTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
else if(result == string_table["preferences"]) {
|
||||
preferences::show_preferences_dialog(gui);
|
||||
}
|
||||
|
||||
else if(result == string_table["end_turn"]) {
|
||||
recorder.end_turn();
|
||||
return;
|
||||
}
|
||||
|
||||
else if(result == string_table["scenario_objectives"]) {
|
||||
static const std::string no_objectives(
|
||||
string_table["no_objectives"]);
|
||||
const std::string& id = level->values["id"];
|
||||
const std::string& lang_objectives =
|
||||
string_table[id + "_objectives"];
|
||||
|
||||
const std::string& objectives = lang_objectives.empty() ?
|
||||
level->values["objectives"] : lang_objectives;
|
||||
gui::show_dialog(
|
||||
gui, NULL, "",
|
||||
objectives.empty() ? no_objectives : objectives,
|
||||
gui::OK_ONLY);
|
||||
}
|
||||
|
||||
else if(result == string_table["recall"]) {
|
||||
//sort the available units into order by value
|
||||
//so that the most valuable units are shown first
|
||||
std::sort(state_of_game.available_units.begin(),
|
||||
state_of_game.available_units.end(),
|
||||
compare_unit_values());
|
||||
|
||||
gui.draw(); //clear the old menu
|
||||
if(state_of_game.available_units.empty()) {
|
||||
gui::show_dialog(gui,NULL,"",string_table["no_recall"]);
|
||||
} else if(current_team.gold() < game_config::recall_cost) {
|
||||
std::stringstream msg;
|
||||
msg << string_table["not_enough_gold_to_recall_1"]
|
||||
<< " " << game_config::recall_cost << " "
|
||||
<< string_table["not_enough_gold_to_recall_2"];
|
||||
gui::show_dialog(gui, NULL,"",msg.str());
|
||||
} else {
|
||||
std::vector<std::string> options;
|
||||
for(std::vector<unit>::const_iterator unit =
|
||||
state_of_game.available_units.begin();
|
||||
unit != state_of_game.available_units.end(); ++unit) {
|
||||
std::stringstream option;
|
||||
option << unit->type().language_name() << ","
|
||||
<< string_table["level"] << ": "
|
||||
<< unit->type().level() << ","
|
||||
<< string_table["xp"] << ": "
|
||||
<< unit->experience() << "/"
|
||||
<< unit->max_experience();
|
||||
options.push_back(option.str());
|
||||
}
|
||||
|
||||
const int res = gui::show_dialog(gui,NULL,"",
|
||||
string_table["select_unit"] + ":\n",
|
||||
gui::OK_CANCEL,&options,
|
||||
&state_of_game.available_units);
|
||||
if(res >= 0) {
|
||||
|
||||
const std::string err = recruit_unit(map,team_num,
|
||||
units,state_of_game.available_units[res],
|
||||
last_hex,&gui);
|
||||
if(!err.empty()) {
|
||||
gui::show_dialog(gui,NULL,"",err,gui::OK_ONLY);
|
||||
} else {
|
||||
current_team.spend_gold(
|
||||
game_config::recall_cost);
|
||||
state_of_game.available_units.erase(
|
||||
state_of_game.available_units.begin()+res);
|
||||
|
||||
recorder.add_recall(res,last_hex);
|
||||
|
||||
undo_stack.clear();
|
||||
redo_stack.clear();
|
||||
|
||||
gui.invalidate_game_status();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(result == string_table["recruit"]) {
|
||||
|
||||
std::vector<unit> sample_units;
|
||||
|
||||
gui.draw(); //clear the old menu
|
||||
std::vector<std::string> item_keys;
|
||||
std::vector<std::string> items;
|
||||
const std::set<std::string>& recruits
|
||||
= current_team.recruits();
|
||||
for(std::set<std::string>::const_iterator it =
|
||||
recruits.begin(); it != recruits.end(); ++it) {
|
||||
item_keys.push_back(*it);
|
||||
const std::map<std::string,unit_type>::const_iterator
|
||||
u_type = gameinfo.unit_types.find(*it);
|
||||
if(u_type == gameinfo.unit_types.end()) {
|
||||
std::cerr << "could not find " << *it << std::endl;
|
||||
assert(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
const unit_type& type = u_type->second;
|
||||
std::stringstream description;
|
||||
|
||||
description << type.language_name() << ": "
|
||||
<< type.cost() << " gold";
|
||||
items.push_back(description.str());
|
||||
sample_units.push_back(unit(&type,team_num));
|
||||
}
|
||||
|
||||
const int recruit_res =
|
||||
gui::show_dialog(gui,NULL,"",
|
||||
string_table["recruit_unit"] + ":\n",
|
||||
gui::OK_CANCEL,&items,&sample_units);
|
||||
if(recruit_res != -1) {
|
||||
const std::string& name = item_keys[recruit_res];
|
||||
const std::map<std::string,unit_type>::const_iterator
|
||||
u_type = gameinfo.unit_types.find(name);
|
||||
assert(u_type != gameinfo.unit_types.end());
|
||||
|
||||
if(u_type->second.cost() > current_team.gold()) {
|
||||
gui::show_dialog(gui,NULL,"",
|
||||
string_table["not_enough_gold_to_recruit"],
|
||||
gui::OK_ONLY);
|
||||
} else {
|
||||
recorder.add_recruit(recruit_res,last_hex);
|
||||
|
||||
//create a unit with traits
|
||||
unit new_unit(&(u_type->second),team_num,true);
|
||||
const std::string& msg =
|
||||
recruit_unit(map,team_num,units,new_unit,
|
||||
last_hex,&gui);
|
||||
if(msg.empty())
|
||||
current_team.spend_gold(u_type->second.cost());
|
||||
else
|
||||
gui::show_dialog(gui,NULL,"",msg,gui::OK_ONLY);
|
||||
|
||||
undo_stack.clear();
|
||||
redo_stack.clear();
|
||||
|
||||
gui.invalidate_game_status();
|
||||
}
|
||||
}
|
||||
} else if(result == string_table["unit_list"]) {
|
||||
const std::string heading = string_table["name"] + "," +
|
||||
string_table["hp"] + "," +
|
||||
string_table["xp"] + "," +
|
||||
string_table["moves"] + "," +
|
||||
string_table["location"];
|
||||
|
||||
std::vector<std::string> items;
|
||||
items.push_back(heading);
|
||||
|
||||
std::vector<unit> units_list;
|
||||
for(units_map::const_iterator i = units.begin();
|
||||
i != units.end(); ++i) {
|
||||
if(i->second.side() != team_num)
|
||||
continue;
|
||||
|
||||
std::stringstream row;
|
||||
row << i->second.name() << "," << i->second.hitpoints()
|
||||
<< "/" << i->second.max_hitpoints() << ","
|
||||
<< i->second.experience() << "/"
|
||||
<< i->second.max_experience() << ","
|
||||
<< i->second.movement_left() << "/"
|
||||
<< i->second.total_movement() << ","
|
||||
<< i->first.x << "-" << i->first.y;
|
||||
|
||||
items.push_back(row.str());
|
||||
|
||||
//extra unit for the first row to make up the heading
|
||||
if(units_list.empty())
|
||||
units_list.push_back(i->second);
|
||||
|
||||
units_list.push_back(i->second);
|
||||
}
|
||||
|
||||
gui::show_dialog(gui,NULL,string_table["unit_list"],"",
|
||||
gui::OK_ONLY,&items,&units_list);
|
||||
} else if(result == string_table["save_game"]) {
|
||||
std::stringstream stream;
|
||||
stream << string_table["scenario"]
|
||||
<< " " << (state_of_game.scenario+1)
|
||||
<< " " << string_table["turn"]
|
||||
<< " " << status.turn();
|
||||
std::string label = stream.str();
|
||||
|
||||
const int res = gui::show_dialog(gui, NULL, "", "",
|
||||
gui::OK_CANCEL,NULL,NULL,
|
||||
string_table["save_game_label"],
|
||||
&label);
|
||||
|
||||
if(res == 0) {
|
||||
recorder.save_game(gameinfo,label);
|
||||
gui::show_dialog(gui,NULL,"",
|
||||
string_table["save_confirm_message"], gui::OK_ONLY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
right_button = new_right_button;
|
||||
|
||||
if(key[KEY_UP] || mousey == 0)
|
||||
gui.scroll(0.0,-scroll_speed);
|
||||
|
||||
if(key[KEY_DOWN] || mousey == gui.y()-1)
|
||||
gui.scroll(0.0,scroll_speed);
|
||||
|
||||
if(key[KEY_LEFT] || mousex == 0)
|
||||
gui.scroll(-scroll_speed,0.0);
|
||||
|
||||
if(key[KEY_RIGHT] || mousex == gui.x()-1)
|
||||
gui.scroll(scroll_speed,0.0);
|
||||
|
||||
if(command == HOTKEY_NULL)
|
||||
command = check_keys(gui);
|
||||
|
||||
units_map::const_iterator un = units.find(new_hex);
|
||||
if(un == units.end())
|
||||
un = units.find(selected_hex);
|
||||
|
||||
if(un != units.end() && command == HOTKEY_ATTACK_RESISTANCE) {
|
||||
gui.draw();
|
||||
|
||||
std::vector<std::string> items;
|
||||
items.push_back(string_table["attack_type"] + "," +
|
||||
string_table["attack_resistance"]);
|
||||
const std::map<std::string,std::string>& table =
|
||||
un->second.type().movement_type().damage_table();
|
||||
for(std::map<std::string,std::string>::const_iterator i
|
||||
= table.begin(); i != table.end(); ++i) {
|
||||
double resistance = atof(i->second.c_str());
|
||||
|
||||
//if resistance is less than 0, display in red
|
||||
std::string prefix = "";
|
||||
if(resistance > 1.0) {
|
||||
prefix = "#";
|
||||
}
|
||||
|
||||
const int resist=int(100.0-ceil(100.0*resistance));
|
||||
|
||||
std::stringstream str;
|
||||
str << i->first << "," << prefix << resist << "%";
|
||||
items.push_back(str.str());
|
||||
}
|
||||
|
||||
const std::vector<unit> units_list(items.size(),
|
||||
un->second);
|
||||
SDL_Surface* const unit_image =
|
||||
gui.getImage(un->second.type().image_profile(),display::UNSCALED);
|
||||
gui::show_dialog(gui,unit_image,
|
||||
un->second.type().language_name(),
|
||||
"Unit resistance table",
|
||||
gui::MESSAGE,&items,&units_list);
|
||||
}
|
||||
|
||||
if(un != units.end() && command == HOTKEY_TERRAIN_TABLE) {
|
||||
gui.draw();
|
||||
|
||||
std::vector<std::string> items;
|
||||
items.push_back(string_table["terrain"] + "," +
|
||||
string_table["movement"] + "," +
|
||||
string_table["defense"]);
|
||||
|
||||
const unit_type& type = un->second.type();
|
||||
const unit_movement_type& move_type =
|
||||
type.movement_type();
|
||||
const std::vector<gamemap::TERRAIN>& terrains =
|
||||
map.get_terrain_precedence();
|
||||
for(std::vector<gamemap::TERRAIN>::const_iterator t =
|
||||
terrains.begin(); t != terrains.end(); ++t) {
|
||||
const terrain_type& info = map.get_terrain_info(*t);
|
||||
if(!info.is_alias()) {
|
||||
const std::string& name = map.terrain_name(*t);
|
||||
const std::string& lang_name = string_table[name];
|
||||
const int moves = move_type.movement_cost(map,*t);
|
||||
|
||||
const double defense = move_type.defense_modifier(map,*t);
|
||||
|
||||
const int def = int(100.0-ceil(100.0*defense));
|
||||
|
||||
std::stringstream str;
|
||||
str << lang_name << ",";
|
||||
if(moves < 10)
|
||||
str << moves;
|
||||
else
|
||||
str << "--";
|
||||
|
||||
str << "," << def << "%";
|
||||
|
||||
items.push_back(str.str());
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<unit> units_list(items.size(),un->second);
|
||||
SDL_Surface* const unit_image =
|
||||
gui.getImage(un->second.type().image_profile(),display::UNSCALED);
|
||||
gui::show_dialog(gui,unit_image,un->second.type().language_name(),
|
||||
string_table["terrain_info"],
|
||||
gui::MESSAGE,&items,&units_list);
|
||||
}
|
||||
|
||||
if(command == HOTKEY_END_UNIT_TURN) {
|
||||
const units_map::iterator un = units.find(selected_hex);
|
||||
if(un != units.end() && un->second.side() == team_num &&
|
||||
un->second.movement_left() > 0) {
|
||||
std::vector<gamemap::location> steps;
|
||||
steps.push_back(selected_hex);
|
||||
undo_stack.push_back(undo_action(
|
||||
steps,un->second.movement_left(),-1));
|
||||
redo_stack.clear();
|
||||
un->second.set_movement(0);
|
||||
gui.draw_tile(selected_hex.x,selected_hex.y);
|
||||
|
||||
gui.set_paths(NULL);
|
||||
current_paths = paths();
|
||||
recorder.add_movement(selected_hex,selected_hex);
|
||||
|
||||
command = HOTKEY_CYCLE_UNITS;
|
||||
}
|
||||
}
|
||||
|
||||
//look for the next unit that is unmoved on our side
|
||||
if(command == HOTKEY_CYCLE_UNITS) {
|
||||
units_map::const_iterator it = units.find(next_unit);
|
||||
if(it != units.end()) {
|
||||
for(++it; it != units.end(); ++it) {
|
||||
if(it->second.side() == team_num &&
|
||||
it->second.movement_left() > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(it == units.end()) {
|
||||
for(it = units.begin(); it != units.end();
|
||||
++it) {
|
||||
if(it->second.side() == team_num &&
|
||||
it->second.movement_left() > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(it != units.end()) {
|
||||
const bool ignore_zocs =
|
||||
it->second.type().is_skirmisher();
|
||||
const bool teleport = it->second.type().teleports();
|
||||
current_paths = paths(map,gameinfo,units,
|
||||
it->first,teams,ignore_zocs,teleport);
|
||||
gui.set_paths(¤t_paths);
|
||||
|
||||
gui.scroll_to_tile(it->first.x,it->first.y,
|
||||
display::WARP);
|
||||
}
|
||||
|
||||
if(it != units.end()) {
|
||||
next_unit = it->first;
|
||||
selected_hex = next_unit;
|
||||
gui.select_hex(selected_hex);
|
||||
} else
|
||||
next_unit = gamemap::location();
|
||||
}
|
||||
|
||||
if(command == HOTKEY_LEADER) {
|
||||
for(units_map::const_iterator i = units.begin(); i != units.end();
|
||||
++i) {
|
||||
if(i->second.side() == team_num && i->second.can_recruit()) {
|
||||
gui.scroll_to_tile(i->first.x,i->first.y,display::WARP);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//undo
|
||||
if(command == HOTKEY_UNDO && !undo_stack.empty()) {
|
||||
const int starting_moves = undo_stack.back().starting_moves;
|
||||
std::vector<gamemap::location> route = undo_stack.back().route;
|
||||
std::reverse(route.begin(),route.end());
|
||||
const units_map::iterator u = units.find(route.front());
|
||||
if(u == units.end()) {
|
||||
assert(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(map[route.front().x][route.front().y] == gamemap::TOWER) {
|
||||
get_tower(route.front(),teams,
|
||||
undo_stack.back().original_village_owner);
|
||||
}
|
||||
|
||||
undo_stack.back().starting_moves = u->second.movement_left();
|
||||
|
||||
unit un = u->second;
|
||||
units.erase(u);
|
||||
gui.move_unit(route,un);
|
||||
un.set_movement(starting_moves);
|
||||
units.insert(std::pair<gamemap::location,unit>(route.back(),un));
|
||||
gui.invalidate_unit();
|
||||
gui.draw_tile(route.back().x,route.back().y);
|
||||
|
||||
redo_stack.push_back(undo_stack.back());
|
||||
undo_stack.pop_back();
|
||||
|
||||
gui.set_paths(NULL);
|
||||
current_paths = paths();
|
||||
|
||||
recorder.undo();
|
||||
}
|
||||
|
||||
if(command == HOTKEY_REDO && !redo_stack.empty()) {
|
||||
const int starting_moves = redo_stack.back().starting_moves;
|
||||
std::vector<gamemap::location> route = redo_stack.back().route;
|
||||
const units_map::iterator u = units.find(route.front());
|
||||
if(u == units.end()) {
|
||||
assert(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
redo_stack.back().starting_moves = u->second.movement_left();
|
||||
|
||||
unit un = u->second;
|
||||
units.erase(u);
|
||||
gui.move_unit(route,un);
|
||||
un.set_movement(starting_moves);
|
||||
units.insert(std::pair<gamemap::location,unit>(route.back(),un));
|
||||
gui.invalidate_unit();
|
||||
|
||||
recorder.add_movement(route.front(),route.back());
|
||||
|
||||
if(map[route.back().x][route.back().y] == gamemap::TOWER) {
|
||||
get_tower(route.back(),teams,un.side()-1);
|
||||
}
|
||||
|
||||
gui.draw_tile(route.back().x,route.back().y);
|
||||
|
||||
undo_stack.push_back(redo_stack.back());
|
||||
redo_stack.pop_back();
|
||||
}
|
||||
|
||||
gui.draw();
|
||||
|
||||
game_events::pump();
|
||||
}
|
||||
}
|
53
src/playturn.hpp
Normal file
53
src/playturn.hpp
Normal file
|
@ -0,0 +1,53 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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 PLAYTURN_HPP_INCLUDED
|
||||
#define PLAYTURN_HPP_INCLUDED
|
||||
|
||||
#include "actions.hpp"
|
||||
#include "ai.hpp"
|
||||
#include "config.hpp"
|
||||
#include "dialogs.hpp"
|
||||
#include "display.hpp"
|
||||
#include "game_config.hpp"
|
||||
#include "game_events.hpp"
|
||||
#include "gamestatus.hpp"
|
||||
#include "key.hpp"
|
||||
#include "menu.hpp"
|
||||
#include "pathfind.hpp"
|
||||
#include "team.hpp"
|
||||
#include "unit_types.hpp"
|
||||
#include "unit.hpp"
|
||||
#include "video.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
struct paths_wiper
|
||||
{
|
||||
paths_wiper(display& gui) : gui_(gui)
|
||||
{}
|
||||
|
||||
~paths_wiper() { gui_.set_paths(NULL); }
|
||||
|
||||
private:
|
||||
display& gui_;
|
||||
};
|
||||
|
||||
void play_turn(game_data& gameinfo, game_state& state_of_game,
|
||||
gamestatus& status, config& terrain_config, config* level,
|
||||
CVideo& video, CKey& key, display& gui,
|
||||
game_events::manager& events_manager, gamemap& map,
|
||||
std::vector<team>& teams, int team_num,
|
||||
std::map<gamemap::location,unit>& units);
|
||||
|
||||
#endif
|
393
src/preferences.cpp
Normal file
393
src/preferences.cpp
Normal file
|
@ -0,0 +1,393 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "filesystem.hpp"
|
||||
#include "font.hpp"
|
||||
#include "language.hpp"
|
||||
#include "menu.hpp"
|
||||
#include "preferences.hpp"
|
||||
#include "sound.hpp"
|
||||
#include "util.hpp"
|
||||
#include "widgets/button.hpp"
|
||||
#include "widgets/slider.hpp"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <sstream>
|
||||
|
||||
namespace {
|
||||
|
||||
config prefs;
|
||||
|
||||
display* disp;
|
||||
|
||||
}
|
||||
|
||||
namespace preferences {
|
||||
|
||||
manager::manager()
|
||||
{
|
||||
prefs.read(read_file(get_prefs_file()));
|
||||
|
||||
set_music_volume(music_volume());
|
||||
set_sound_volume(sound_volume());
|
||||
}
|
||||
|
||||
manager::~manager()
|
||||
{
|
||||
write_file(get_prefs_file(),prefs.write());
|
||||
}
|
||||
|
||||
display_manager::display_manager(display* d)
|
||||
{
|
||||
disp = d;
|
||||
|
||||
set_grid(grid());
|
||||
set_turbo(turbo());
|
||||
set_fullscreen(fullscreen());
|
||||
}
|
||||
|
||||
display_manager::~display_manager()
|
||||
{
|
||||
disp = NULL;
|
||||
}
|
||||
|
||||
bool fullscreen()
|
||||
{
|
||||
const string_map::const_iterator fullscreen =
|
||||
prefs.values.find("fullscreen");
|
||||
return fullscreen == prefs.values.end() || fullscreen->second == "true";
|
||||
}
|
||||
|
||||
void set_fullscreen(bool ison)
|
||||
{
|
||||
prefs.values["fullscreen"] = (ison ? "true" : "false");
|
||||
|
||||
if(disp != NULL) {
|
||||
const std::pair<int,int>& res = resolution();
|
||||
CVideo& video = disp->video();
|
||||
if(video.isFullScreen() != ison) {
|
||||
const int flags = ison ? FULL_SCREEN : 0;
|
||||
if(video.modePossible(res.first,res.second,16,flags)) {
|
||||
video.setMode(res.first,res.second,16,flags);
|
||||
disp->redraw_everything();
|
||||
} else {
|
||||
gui::show_dialog(*disp,NULL,"",string_table["video_mode_fail"],
|
||||
gui::MESSAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<int,int> resolution()
|
||||
{
|
||||
const string_map::const_iterator x = prefs.values.find("xresolution");
|
||||
const string_map::const_iterator y = prefs.values.find("yresolution");
|
||||
if(x != prefs.values.end() && y != prefs.values.end() &&
|
||||
x->second.empty() == false && y->second.empty() == false) {
|
||||
return std::pair<int,int>(maximum(atoi(x->second.c_str()),1024),
|
||||
maximum(atoi(y->second.c_str()),768));
|
||||
} else {
|
||||
return std::pair<int,int>(1024,768);
|
||||
}
|
||||
}
|
||||
|
||||
void set_resolution(const std::pair<int,int>& res)
|
||||
{
|
||||
if(disp != NULL) {
|
||||
CVideo& video = disp->video();
|
||||
const int flags = video.isFullScreen() ? FULL_SCREEN : 0;
|
||||
if(video.modePossible(res.first,res.second,16,flags)) {
|
||||
|
||||
video.setMode(res.first,res.second,16,flags);
|
||||
disp->redraw_everything();
|
||||
|
||||
char buf[50];
|
||||
sprintf(buf,"%d",res.first);
|
||||
prefs.values["xresolution"] = buf;
|
||||
sprintf(buf,"%d",res.second);
|
||||
prefs.values["yresolution"] = buf;
|
||||
} else {
|
||||
gui::show_dialog(*disp,NULL,"",string_table["video_mode_fail"],
|
||||
gui::MESSAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool turbo()
|
||||
{
|
||||
const string_map::const_iterator turbo = prefs.values.find("turbo");
|
||||
return turbo != prefs.values.end() && turbo->second == "true";
|
||||
}
|
||||
|
||||
void set_turbo(bool ison)
|
||||
{
|
||||
prefs.values["turbo"] = (ison ? "true" : "false");
|
||||
|
||||
if(disp != NULL) {
|
||||
disp->set_turbo(ison);
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& locale()
|
||||
{
|
||||
return prefs.values["locale"];
|
||||
}
|
||||
|
||||
void set_locale(const std::string& s)
|
||||
{
|
||||
prefs.values["locale"] = s;
|
||||
}
|
||||
|
||||
double music_volume()
|
||||
{
|
||||
static const double default_value = 1.0;
|
||||
const string_map::const_iterator volume = prefs.values.find("music_volume");
|
||||
if(volume != prefs.values.end() && volume->second.empty() == false)
|
||||
return atof(volume->second.c_str());
|
||||
else
|
||||
return default_value;
|
||||
}
|
||||
|
||||
void set_music_volume(double vol)
|
||||
{
|
||||
std::stringstream stream;
|
||||
stream << vol;
|
||||
prefs.values["music_volume"] = stream.str();
|
||||
|
||||
sound::set_music_volume(vol);
|
||||
}
|
||||
|
||||
double sound_volume()
|
||||
{
|
||||
static const double default_value = 1.0;
|
||||
const string_map::const_iterator volume = prefs.values.find("sound_volume");
|
||||
if(volume != prefs.values.end() && volume->second.empty() == false)
|
||||
return atof(volume->second.c_str());
|
||||
else
|
||||
return default_value;
|
||||
}
|
||||
|
||||
void set_sound_volume(double vol)
|
||||
{
|
||||
std::stringstream stream;
|
||||
stream << vol;
|
||||
prefs.values["sound_volume"] = stream.str();
|
||||
|
||||
sound::set_sound_volume(vol);
|
||||
}
|
||||
|
||||
bool grid()
|
||||
{
|
||||
const string_map::const_iterator turbo = prefs.values.find("grid");
|
||||
return turbo != prefs.values.end() && turbo->second == "true";
|
||||
}
|
||||
|
||||
void set_grid(bool ison)
|
||||
{
|
||||
prefs.values["grid"] = (ison ? "true" : "false");
|
||||
|
||||
if(disp != NULL) {
|
||||
disp->set_grid(ison);
|
||||
}
|
||||
}
|
||||
|
||||
void show_preferences_dialog(display& disp)
|
||||
{
|
||||
const int border_size = 6;
|
||||
const int xpos = disp.x()/2 - 300;
|
||||
const int ypos = disp.y()/2 - 200;
|
||||
const int width = 600;
|
||||
const int height = 400;
|
||||
|
||||
disp.invalidate_all();
|
||||
disp.draw();
|
||||
|
||||
SDL_Rect clip_rect = {0,0,disp.x(),disp.y()};
|
||||
SDL_Rect title_rect = font::draw_text(NULL,clip_rect,16,font::NORMAL_COLOUR,
|
||||
string_table["preferences"],0,0);
|
||||
|
||||
gui::button close_button(disp,string_table["close_window"]);
|
||||
|
||||
close_button.set_x(xpos + width/2 - close_button.width()/2);
|
||||
close_button.set_y(ypos + height - close_button.height()-14);
|
||||
|
||||
const std::string& music_label = string_table["music_volume"];
|
||||
const std::string& sound_label = string_table["sound_volume"];
|
||||
|
||||
SDL_Rect music_rect = {0,0,0,0};
|
||||
music_rect = font::draw_text(NULL,clip_rect,14,font::NORMAL_COLOUR,
|
||||
music_label,0,0);
|
||||
|
||||
SDL_Rect sound_rect = {0,0,0,0};
|
||||
sound_rect = font::draw_text(NULL,clip_rect,14,font::NORMAL_COLOUR,
|
||||
sound_label,0,0);
|
||||
|
||||
const int text_right = xpos + maximum(music_rect.w,sound_rect.w) + 5;
|
||||
|
||||
const int music_pos = ypos + title_rect.h + 20;
|
||||
const int sound_pos = music_pos + 50;
|
||||
|
||||
music_rect.x = text_right - music_rect.w;
|
||||
music_rect.y = music_pos;
|
||||
|
||||
sound_rect.x = text_right - sound_rect.w;
|
||||
sound_rect.y = sound_pos;
|
||||
|
||||
const int slider_left = text_right + 10;
|
||||
const int slider_right = xpos + width - 5;
|
||||
if(slider_left >= slider_right)
|
||||
return;
|
||||
|
||||
SDL_Rect slider_rect = { slider_left,sound_pos,slider_right-slider_left,10};
|
||||
gui::slider sound_slider(disp,slider_rect,sound_volume());
|
||||
|
||||
slider_rect.y = music_pos;
|
||||
gui::slider music_slider(disp,slider_rect,music_volume());
|
||||
|
||||
gui::button fullscreen_button(disp,string_table["full_screen"],
|
||||
gui::button::TYPE_CHECK);
|
||||
|
||||
fullscreen_button.set_check(fullscreen());
|
||||
|
||||
fullscreen_button.set_x(slider_left);
|
||||
fullscreen_button.set_y(sound_pos + 80);
|
||||
|
||||
gui::button turbo_button(disp,string_table["speed_turbo"],
|
||||
gui::button::TYPE_CHECK);
|
||||
turbo_button.set_check(turbo());
|
||||
|
||||
turbo_button.set_x(slider_left);
|
||||
turbo_button.set_y(sound_pos + 80 + 50);
|
||||
|
||||
gui::button grid_button(disp,string_table["grid_button"],
|
||||
gui::button::TYPE_CHECK);
|
||||
grid_button.set_check(grid());
|
||||
|
||||
grid_button.set_x(slider_left);
|
||||
grid_button.set_y(sound_pos + 80 + 100);
|
||||
|
||||
gui::button resolution_button(disp,"Video Mode");
|
||||
resolution_button.set_x(slider_left);
|
||||
resolution_button.set_y(sound_pos + 80 + 150);
|
||||
|
||||
bool redraw_all = true;
|
||||
|
||||
for(;;) {
|
||||
int mousex, mousey;
|
||||
const int mouse_flags = SDL_GetMouseState(&mousex,&mousey);
|
||||
|
||||
const bool left_button = mouse_flags&SDL_BUTTON_LMASK;
|
||||
const bool right_button = mouse_flags&SDL_BUTTON_RMASK;
|
||||
|
||||
if(close_button.process(mousex,mousey,left_button)) {
|
||||
break;
|
||||
}
|
||||
|
||||
const double new_music=music_slider.process(mousex,mousey,left_button);
|
||||
const double new_sound=sound_slider.process(mousex,mousey,left_button);
|
||||
|
||||
if(new_sound >= 0.0) {
|
||||
set_sound_volume(new_sound);
|
||||
}
|
||||
|
||||
if(new_music >= 0.0) {
|
||||
set_music_volume(new_music);
|
||||
}
|
||||
|
||||
if(fullscreen_button.process(mousex,mousey,left_button)) {
|
||||
set_fullscreen(fullscreen_button.checked());
|
||||
redraw_all = true;
|
||||
}
|
||||
|
||||
if(redraw_all) {
|
||||
gui::draw_dialog_frame(xpos,ypos,width,height,disp);
|
||||
sound_slider.background_changed();
|
||||
music_slider.background_changed();
|
||||
sound_slider.draw();
|
||||
music_slider.draw();
|
||||
fullscreen_button.draw();
|
||||
turbo_button.draw();
|
||||
grid_button.draw();
|
||||
close_button.draw();
|
||||
resolution_button.draw();
|
||||
|
||||
font::draw_text(&disp,clip_rect,14,font::NORMAL_COLOUR,music_label,
|
||||
music_rect.x,music_rect.y);
|
||||
|
||||
font::draw_text(&disp,clip_rect,14,font::NORMAL_COLOUR,sound_label,
|
||||
sound_rect.x,sound_rect.y);
|
||||
|
||||
font::draw_text(&disp,clip_rect,18,font::NORMAL_COLOUR,
|
||||
string_table["preferences"],
|
||||
xpos+(width-title_rect.w)/2,ypos+10);
|
||||
|
||||
redraw_all = false;
|
||||
}
|
||||
|
||||
if(turbo_button.process(mousex,mousey,left_button)) {
|
||||
set_turbo(turbo_button.checked());
|
||||
}
|
||||
|
||||
if(grid_button.process(mousex,mousey,left_button)) {
|
||||
set_grid(grid_button.checked());
|
||||
}
|
||||
|
||||
if(resolution_button.process(mousex,mousey,left_button)) {
|
||||
show_video_mode_dialog(disp);
|
||||
break;
|
||||
}
|
||||
|
||||
disp.update_display();
|
||||
|
||||
SDL_Delay(10);
|
||||
SDL_PumpEvents();
|
||||
}
|
||||
|
||||
disp.invalidate_all();
|
||||
disp.draw();
|
||||
}
|
||||
|
||||
void show_video_mode_dialog(display& disp)
|
||||
{
|
||||
std::vector<std::pair<int,int> > resolutions;
|
||||
std::vector<std::string> options;
|
||||
|
||||
CVideo& video = disp.video();
|
||||
SDL_Rect** modes = SDL_ListModes(video.getSurface()->format,FULL_SCREEN);
|
||||
if(reinterpret_cast<int>(modes) == -1 || modes == NULL)
|
||||
return;
|
||||
|
||||
for(int i = 0; modes[i] != NULL; ++i) {
|
||||
if(modes[i]->w >= 1024 && modes[i]->h >= 768) {
|
||||
const std::pair<int,int> new_res(modes[i]->w,modes[i]->h);
|
||||
if(std::count(resolutions.begin(),resolutions.end(),new_res) > 0)
|
||||
continue;
|
||||
|
||||
resolutions.push_back(new_res);
|
||||
|
||||
std::stringstream option;
|
||||
option << modes[i]->w << "x" << modes[i]->h;
|
||||
options.push_back(option.str());
|
||||
}
|
||||
}
|
||||
|
||||
if(resolutions.size() < 2)
|
||||
return;
|
||||
|
||||
const int result = gui::show_dialog(disp,NULL,"","Choose Resolution",
|
||||
gui::MESSAGE,&options);
|
||||
if(result >= 0 && result < resolutions.size()) {
|
||||
set_resolution(resolutions[result]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
61
src/preferences.hpp
Normal file
61
src/preferences.hpp
Normal file
|
@ -0,0 +1,61 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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 PREFERENCES_HPP_INCLUDED
|
||||
#define PREFERENCES_HPP_INCLUDED
|
||||
|
||||
#include "config.hpp"
|
||||
#include "display.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace preferences {
|
||||
|
||||
struct manager
|
||||
{
|
||||
manager();
|
||||
~manager();
|
||||
};
|
||||
|
||||
struct display_manager
|
||||
{
|
||||
display_manager(display* disp);
|
||||
~display_manager();
|
||||
};
|
||||
|
||||
bool fullscreen();
|
||||
void set_fullscreen(bool ison);
|
||||
|
||||
std::pair<int,int> resolution();
|
||||
void set_resolution(const std::pair<int,int>& res);
|
||||
|
||||
bool turbo();
|
||||
void set_turbo(bool ison);
|
||||
|
||||
const std::string& locale();
|
||||
void set_locale(const std::string& s);
|
||||
|
||||
double music_volume();
|
||||
void set_music_volume(double vol);
|
||||
|
||||
double sound_volume();
|
||||
void set_sound_volume(double vol);
|
||||
|
||||
bool grid();
|
||||
void set_grid(bool ison);
|
||||
|
||||
void show_preferences_dialog(display& disp);
|
||||
void show_video_mode_dialog(display& disp);
|
||||
}
|
||||
|
||||
#endif
|
483
src/replay.cpp
Normal file
483
src/replay.cpp
Normal file
|
@ -0,0 +1,483 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "actions.hpp"
|
||||
#include "ai.hpp"
|
||||
#include "ai_attack.hpp"
|
||||
#include "ai_move.hpp"
|
||||
#include "dialogs.hpp"
|
||||
#include "game_config.hpp"
|
||||
#include "log.hpp"
|
||||
#include "menu.hpp"
|
||||
#include "pathfind.hpp"
|
||||
#include "playlevel.hpp"
|
||||
#include "playturn.hpp"
|
||||
#include "replay.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <deque>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
replay recorder;
|
||||
|
||||
replay::replay() : pos_(0), current_(NULL), skip_(0)
|
||||
{}
|
||||
|
||||
replay::replay(config& cfg) : cfg_(cfg), pos_(0), current_(NULL), skip_(0)
|
||||
{}
|
||||
|
||||
config& replay::get_config()
|
||||
{
|
||||
return cfg_;
|
||||
}
|
||||
|
||||
void replay::set_save_info(const game_state& save)
|
||||
{
|
||||
saveInfo_ = save;
|
||||
}
|
||||
|
||||
const game_state& replay::get_save_info() const
|
||||
{
|
||||
return saveInfo_;
|
||||
}
|
||||
|
||||
void replay::set_skip(int turns_to_skip)
|
||||
{
|
||||
skip_ = turns_to_skip;
|
||||
}
|
||||
|
||||
void replay::next_skip()
|
||||
{
|
||||
if(skip_ > 0)
|
||||
--skip_;
|
||||
}
|
||||
|
||||
bool replay::skipping() const
|
||||
{
|
||||
return skip_ != 0;
|
||||
}
|
||||
|
||||
void replay::save_game(game_data& data, const std::string& label)
|
||||
{
|
||||
saveInfo_.replay_data = cfg_;
|
||||
saveInfo_.label = label;
|
||||
|
||||
::save_game(saveInfo_);
|
||||
|
||||
saveInfo_.replay_data = config();
|
||||
}
|
||||
|
||||
void replay::add_recruit(int value, const gamemap::location& loc)
|
||||
{
|
||||
config* const cmd = add_command();
|
||||
|
||||
config* const val = new config();
|
||||
|
||||
char buf[100];
|
||||
sprintf(buf,"%d",value);
|
||||
val->values["value"] = buf;
|
||||
|
||||
sprintf(buf,"%d",loc.x+1);
|
||||
val->values["x"] = buf;
|
||||
|
||||
sprintf(buf,"%d",loc.y+1);
|
||||
val->values["y"] = buf;
|
||||
|
||||
cmd->children["recruit"].push_back(val);
|
||||
}
|
||||
|
||||
void replay::add_recall(int value, const gamemap::location& loc)
|
||||
{
|
||||
config* const cmd = add_command();
|
||||
|
||||
config* const val = new config();
|
||||
|
||||
char buf[100];
|
||||
sprintf(buf,"%d",value);
|
||||
val->values["value"] = buf;
|
||||
|
||||
sprintf(buf,"%d",loc.x+1);
|
||||
val->values["x"] = buf;
|
||||
|
||||
sprintf(buf,"%d",loc.y+1);
|
||||
val->values["y"] = buf;
|
||||
|
||||
cmd->children["recall"].push_back(val);
|
||||
}
|
||||
|
||||
void replay::add_movement(const gamemap::location& a,const gamemap::location& b)
|
||||
{
|
||||
add_pos("move",a,b);
|
||||
current_ = NULL;
|
||||
}
|
||||
|
||||
void replay::add_attack(const gamemap::location& a, const gamemap::location& b,
|
||||
int weapon)
|
||||
{
|
||||
add_pos("attack",a,b);
|
||||
char buf[100];
|
||||
sprintf(buf,"%d",weapon);
|
||||
current_->children["attack"][0]->values["weapon"] = buf;
|
||||
}
|
||||
|
||||
void replay::add_pos(const std::string& type,
|
||||
const gamemap::location& a, const gamemap::location& b)
|
||||
{
|
||||
config* const cmd = add_command();
|
||||
config* const move = new config();
|
||||
config* const src = new config();
|
||||
config* const dst = new config();
|
||||
|
||||
char buf[100];
|
||||
sprintf(buf,"%d",a.x+1);
|
||||
src->values["x"] = buf;
|
||||
sprintf(buf,"%d",a.y+1);
|
||||
src->values["y"] = buf;
|
||||
sprintf(buf,"%d",b.x+1);
|
||||
dst->values["x"] = buf;
|
||||
sprintf(buf,"%d",b.y+1);
|
||||
dst->values["y"] = buf;
|
||||
|
||||
move->children["source"].push_back(src);
|
||||
move->children["destination"].push_back(dst);
|
||||
cmd->children[type].push_back(move);
|
||||
|
||||
current_ = cmd;
|
||||
}
|
||||
|
||||
void replay::add_value(const std::string& type, int value)
|
||||
{
|
||||
config* const cmd = add_command();
|
||||
|
||||
config* const val = new config();
|
||||
|
||||
char buf[100];
|
||||
sprintf(buf,"%d",value);
|
||||
val->values["value"] = buf;
|
||||
|
||||
cmd->children[type].push_back(val);
|
||||
}
|
||||
|
||||
void replay::choose_option(int index)
|
||||
{
|
||||
add_value("choose",index);
|
||||
}
|
||||
|
||||
void replay::end_turn()
|
||||
{
|
||||
config* const cmd = add_command();
|
||||
|
||||
cmd->children["end_turn"].push_back(new config());
|
||||
}
|
||||
|
||||
void replay::undo()
|
||||
{
|
||||
std::vector<config*>& cmd = commands();
|
||||
if(!cmd.empty()) {
|
||||
delete cmd.back();
|
||||
cmd.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<config*>& replay::commands()
|
||||
{
|
||||
return cfg_.children["command"];
|
||||
}
|
||||
|
||||
config* replay::add_command()
|
||||
{
|
||||
config* const cmd = new config();
|
||||
commands().push_back(cmd);
|
||||
current_ = cmd;
|
||||
return cmd;
|
||||
}
|
||||
|
||||
int replay::get_random()
|
||||
{
|
||||
if(current_ == NULL) {
|
||||
throw error();
|
||||
}
|
||||
|
||||
//random numbers are in a 'list' meaning that each random
|
||||
//number contains another random numbers unless it's at
|
||||
//the end of the list. Generating a new random number means
|
||||
//nesting a new node inside the current node, and making
|
||||
//the current node the new node
|
||||
std::vector<config*>& random = current_->children["random"];
|
||||
if(random.empty()) {
|
||||
const int res = rand();
|
||||
char buf[100];
|
||||
sprintf(buf,"%d",res);
|
||||
|
||||
current_ = new config();
|
||||
current_->values["value"] = buf;
|
||||
random.push_back(current_);
|
||||
|
||||
return res;
|
||||
} else {
|
||||
const int res = atol(random.front()->values["value"].c_str());
|
||||
current_ = random.front();
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
void replay::start_replay()
|
||||
{
|
||||
pos_ = 0;
|
||||
}
|
||||
|
||||
config* replay::get_next_action()
|
||||
{
|
||||
if(pos_ >= commands().size())
|
||||
return NULL;
|
||||
|
||||
current_ = commands()[pos_];
|
||||
++pos_;
|
||||
return current_;
|
||||
}
|
||||
|
||||
void replay::clear()
|
||||
{
|
||||
cfg_ = config();
|
||||
pos_ = 0;
|
||||
current_ = NULL;
|
||||
skip_ = 0;
|
||||
}
|
||||
|
||||
bool replay::empty()
|
||||
{
|
||||
return commands().empty();
|
||||
}
|
||||
|
||||
bool do_replay(display& disp, const gamemap& map, const game_data& gameinfo,
|
||||
std::map<gamemap::location,unit>& units,
|
||||
std::vector<team>& teams, int team_num, const gamestatus& state,
|
||||
game_state& state_of_game)
|
||||
{
|
||||
update_locker lock_update(disp,recorder.skipping());
|
||||
|
||||
//a list of units that have promoted from the last attack
|
||||
std::deque<gamemap::location> advancing_units;
|
||||
|
||||
team& current_team = teams[team_num-1];
|
||||
|
||||
for(;;) {
|
||||
config* const cfg = recorder.get_next_action();
|
||||
|
||||
std::map<std::string,std::vector<config*> >::iterator it;
|
||||
|
||||
//if we are expecting promotions here
|
||||
if(advancing_units.empty() == false) {
|
||||
if(cfg == NULL ||
|
||||
(it = cfg->children.find("choose")) == cfg->children.end()) {
|
||||
std::cerr << "promotion expected, but none found\n";
|
||||
throw replay::error();
|
||||
}
|
||||
|
||||
const std::map<gamemap::location,unit>::iterator u =
|
||||
units.find(advancing_units.front());
|
||||
assert(u != units.end());
|
||||
|
||||
const std::string& num = it->second.front()->values["value"];
|
||||
const int val = atoi(num.c_str());
|
||||
|
||||
const std::vector<std::string>& options =
|
||||
u->second.type().advances_to();
|
||||
if(val < 0 || val >= options.size()) {
|
||||
std::cerr << "illegal advancement type\n";
|
||||
throw replay::error();
|
||||
}
|
||||
|
||||
advance_unit(gameinfo,units,advancing_units.front(),options[val]);
|
||||
|
||||
advancing_units.pop_front();
|
||||
}
|
||||
|
||||
//if there is nothing more in the records
|
||||
else if(cfg == NULL) {
|
||||
recorder.set_skip(0);
|
||||
return false;
|
||||
}
|
||||
|
||||
//if there is an end turn directive
|
||||
else if(cfg->children.find("end_turn") != cfg->children.end()) {
|
||||
recorder.next_skip();
|
||||
return true;
|
||||
}
|
||||
|
||||
else if((it = cfg->children.find("recruit")) != cfg->children.end()) {
|
||||
assert(!it->second.empty());
|
||||
const std::string& recruit_num=it->second.front()->values["value"];
|
||||
const int val = atoi(recruit_num.c_str());
|
||||
|
||||
const gamemap::location loc(*(it->second.front()));
|
||||
|
||||
const std::set<std::string>& recruits = current_team.recruits();
|
||||
std::set<std::string>::const_iterator itor = recruits.begin();
|
||||
std::advance(itor,val);
|
||||
const std::map<std::string,unit_type>::const_iterator u_type =
|
||||
gameinfo.unit_types.find(*itor);
|
||||
if(u_type == gameinfo.unit_types.end())
|
||||
throw replay::error();
|
||||
|
||||
unit new_unit(&(u_type->second),team_num,true);
|
||||
recruit_unit(map,team_num,units,new_unit,loc);
|
||||
|
||||
current_team.spend_gold(u_type->second.cost());
|
||||
}
|
||||
|
||||
else if((it = cfg->children.find("recall")) != cfg->children.end()) {
|
||||
std::sort(state_of_game.available_units.begin(),
|
||||
state_of_game.available_units.end(),
|
||||
compare_unit_values());
|
||||
|
||||
assert(!it->second.empty());
|
||||
const std::string recall_num = it->second.front()->values["value"];
|
||||
const int val = atoi(recall_num.c_str());
|
||||
|
||||
const gamemap::location loc(*(it->second.front()));
|
||||
|
||||
recruit_unit(map,team_num,units,
|
||||
state_of_game.available_units[val],loc);
|
||||
state_of_game.available_units.erase(
|
||||
state_of_game.available_units.begin()+val);
|
||||
current_team.spend_gold(game_config::recall_cost);
|
||||
}
|
||||
|
||||
else if((it = cfg->children.find("move")) != cfg->children.end()) {
|
||||
assert(!it->second.empty());
|
||||
|
||||
config* const move = it->second.front();
|
||||
|
||||
assert(!move->children["destination"].empty());
|
||||
assert(!move->children["source"].empty());
|
||||
|
||||
const gamemap::location src(*(move->children["source"][0]));
|
||||
const gamemap::location dst(*(move->children["destination"][0]));
|
||||
|
||||
const std::map<gamemap::location,unit>::iterator u=units.find(src);
|
||||
if(u == units.end()) {
|
||||
std::cerr << "unfound location for source of movement: "
|
||||
<< src.x << "," << src.y << "-"
|
||||
<< dst.x << "," << dst.y << "\n";
|
||||
throw replay::error();
|
||||
}
|
||||
|
||||
const bool ignore_zocs = u->second.type().is_skirmisher();
|
||||
const bool teleport = u->second.type().teleports();
|
||||
|
||||
paths paths_list(map,gameinfo,units,src,teams,ignore_zocs,teleport);
|
||||
paths_wiper wiper(disp);
|
||||
|
||||
if(!recorder.skipping()) {
|
||||
disp.set_paths(&paths_list);
|
||||
|
||||
disp.scroll_to_tiles(src.x,src.y,dst.x,dst.y);
|
||||
}
|
||||
|
||||
unit current_unit = u->second;
|
||||
units.erase(u);
|
||||
|
||||
std::map<gamemap::location,paths::route>::iterator rt =
|
||||
paths_list.routes.find(dst);
|
||||
if(rt == paths_list.routes.end()) {
|
||||
std::cerr << "src cannot get to dst: " << paths_list.routes.size() << "\n";
|
||||
throw replay::error();
|
||||
}
|
||||
|
||||
rt->second.steps.push_back(dst);
|
||||
|
||||
if(!recorder.skipping())
|
||||
disp.move_unit(rt->second.steps,current_unit);
|
||||
|
||||
current_unit.set_movement(rt->second.move_left);
|
||||
units.insert(std::pair<gamemap::location,unit>(dst,current_unit));
|
||||
if(map[dst.x][dst.y] == gamemap::TOWER) {
|
||||
current_unit.set_movement(0);
|
||||
get_tower(dst,teams,team_num-1);
|
||||
}
|
||||
|
||||
if(!recorder.skipping()) {
|
||||
disp.draw_tile(dst.x,dst.y);
|
||||
disp.update_display();
|
||||
}
|
||||
|
||||
game_events::fire("moveto",dst);
|
||||
}
|
||||
|
||||
else if((it = cfg->children.find("attack")) != cfg->children.end()) {
|
||||
assert(!it->second.empty());
|
||||
|
||||
config* const move = it->second.front();
|
||||
|
||||
assert(!move->children["destination"].empty());
|
||||
assert(!move->children["source"].empty());
|
||||
|
||||
const gamemap::location src(*(move->children["source"][0]));
|
||||
const gamemap::location dst(*(move->children["destination"][0]));
|
||||
|
||||
const std::string& weapon = move->values["weapon"];
|
||||
const int weapon_num = atoi(weapon.c_str());
|
||||
|
||||
std::map<gamemap::location,unit>::iterator u=units.find(src);
|
||||
if(u == units.end()) {
|
||||
std::cerr << "unfound location for source of attack\n";
|
||||
throw replay::error();
|
||||
}
|
||||
|
||||
if(weapon_num < 0 || weapon_num >= u->second.attacks().size()) {
|
||||
std::cerr << "illegal weapon type in attack\n";
|
||||
throw replay::error();
|
||||
}
|
||||
|
||||
std::map<gamemap::location,unit>::const_iterator tgt =
|
||||
units.find(dst);
|
||||
|
||||
if(tgt == units.end()) {
|
||||
std::cerr << "unfound defender for attack\n";
|
||||
throw replay::error();
|
||||
}
|
||||
|
||||
game_events::fire("attack",src,dst);
|
||||
|
||||
u = units.find(src);
|
||||
tgt = units.find(dst);
|
||||
|
||||
if(u != units.end() && tgt != units.end()) {
|
||||
attack(disp,map,src,dst,weapon_num,units,state,gameinfo,false);
|
||||
const int res = check_victory(units);
|
||||
if(res == 1)
|
||||
throw end_level_exception(VICTORY);
|
||||
else if(res > 1)
|
||||
throw end_level_exception(DEFEAT);
|
||||
}
|
||||
|
||||
u = units.find(src);
|
||||
tgt = units.find(dst);
|
||||
|
||||
if(u != units.end() && u->second.advances() &&
|
||||
u->second.type().advances_to().empty() == false) {
|
||||
advancing_units.push_back(u->first);
|
||||
}
|
||||
|
||||
if(tgt != units.end() && tgt->second.advances() &&
|
||||
tgt->second.type().advances_to().empty() == false) {
|
||||
advancing_units.push_back(tgt->first);
|
||||
}
|
||||
} else {
|
||||
std::cerr << "unrecognized action\n";
|
||||
throw replay::error();
|
||||
}
|
||||
}
|
||||
}
|
85
src/replay.hpp
Normal file
85
src/replay.hpp
Normal file
|
@ -0,0 +1,85 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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 REPLAY_H_INCLUDED
|
||||
#define REPLAY_H_INCLUDED
|
||||
|
||||
#include "config.hpp"
|
||||
#include "display.hpp"
|
||||
#include "map.hpp"
|
||||
|
||||
class replay
|
||||
{
|
||||
public:
|
||||
replay();
|
||||
replay(config& cfg);
|
||||
|
||||
config& get_config();
|
||||
|
||||
void set_save_info(const game_state& save);
|
||||
const game_state& get_save_info() const;
|
||||
|
||||
void set_skip(int turns_to_skip);
|
||||
void next_skip();
|
||||
bool skipping() const;
|
||||
|
||||
void save_game(game_data& data, const std::string& label);
|
||||
|
||||
void add_recruit(int unit_index, const gamemap::location& loc);
|
||||
void add_recall(int unit_index, const gamemap::location& loc);
|
||||
void add_movement(const gamemap::location& a, const gamemap::location& b);
|
||||
void add_attack(const gamemap::location& a, const gamemap::location& b,
|
||||
int weapon);
|
||||
void choose_option(int index);
|
||||
void end_turn();
|
||||
|
||||
void undo();
|
||||
|
||||
int get_random();
|
||||
|
||||
void start_replay();
|
||||
config* get_next_action();
|
||||
|
||||
void clear();
|
||||
bool empty();
|
||||
|
||||
struct error {};
|
||||
|
||||
private:
|
||||
//generic for add_movement and add_attack
|
||||
void add_pos(const std::string& type,
|
||||
const gamemap::location& a, const gamemap::location& b);
|
||||
|
||||
void add_value(const std::string& type, int value);
|
||||
|
||||
std::vector<config*>& commands();
|
||||
config* add_command();
|
||||
config cfg_;
|
||||
unsigned int pos_;
|
||||
|
||||
config* current_;
|
||||
|
||||
game_state saveInfo_;
|
||||
|
||||
int skip_;
|
||||
};
|
||||
|
||||
extern replay recorder;
|
||||
|
||||
//replays up to one turn from the recorder object
|
||||
//returns true if it got to the end of the turn without data running out
|
||||
bool do_replay(display& disp, const gamemap& map, const game_data& gameinfo,
|
||||
std::map<gamemap::location,unit>& units,
|
||||
std::vector<team>& teams, int team_num, const gamestatus& state,
|
||||
game_state& state_of_game);
|
||||
|
||||
#endif
|
178
src/scoped_resource.hpp
Normal file
178
src/scoped_resource.hpp
Normal file
|
@ -0,0 +1,178 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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 SCOPED_RESOURCE_H_INCLUDED
|
||||
#define SCOPED_RESOURCE_H_INCLUDED
|
||||
|
||||
/**
|
||||
* The util namespace should take all classes which are of a generic type,
|
||||
* used to perform common tasks which are not BibleTime-specific. See
|
||||
* @ref scoped_resource for an example.
|
||||
*/
|
||||
namespace util
|
||||
{
|
||||
/**
|
||||
* A class template, scoped_resource, designed to
|
||||
* implement the Resource Acquisition Is Initialization (RAII) approach
|
||||
* to resource management. scoped_resource is designed to be used when
|
||||
* a resource is initialized at the beginning or middle of a scope,
|
||||
* and released at the end of the scope. The template argument
|
||||
* ReleasePolicy is a functor which takes an argument of the
|
||||
* type of the resource, and releases it.
|
||||
*
|
||||
* Usage example, for working with files:
|
||||
*
|
||||
* @code
|
||||
* struct close_file { void operator(int fd) const {close(fd);} };
|
||||
* ...
|
||||
* {
|
||||
* const scoped_resource<int,close_file> file(open("file.txt",O_RDONLY));
|
||||
* read(file, buf, 1000);
|
||||
* } // file is automatically closed here
|
||||
* @endcode
|
||||
*
|
||||
* Note that scoped_resource has an explicit constructor, and prohibits
|
||||
* copy-construction, and thus the initialization syntax, rather than
|
||||
* the assignment syntax must be used when initializing.
|
||||
*
|
||||
* i.e. using scoped_resource<int,close_file> file = open("file.txt",O_RDONLY);
|
||||
* in the above example is illegal.
|
||||
*
|
||||
*/
|
||||
template<typename T,typename ReleasePolicy>
|
||||
class scoped_resource
|
||||
{
|
||||
T resource;
|
||||
ReleasePolicy release;
|
||||
|
||||
//prohibited operations
|
||||
scoped_resource(const scoped_resource&);
|
||||
scoped_resource& operator=(const scoped_resource&);
|
||||
public:
|
||||
typedef T resource_type;
|
||||
typedef ReleasePolicy release_type;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @ param res This is the resource to be managed
|
||||
* @ param rel This is the functor to release the object
|
||||
*/
|
||||
scoped_resource(resource_type res,release_type rel=release_type())
|
||||
: resource(res), release(rel) {}
|
||||
|
||||
/**
|
||||
* The destructor is the main point in this class. It takes care of proper
|
||||
* deletion of the resource, using the provided release policy.
|
||||
*/
|
||||
~scoped_resource()
|
||||
{
|
||||
release(resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* This operator makes sure you can access and use the scoped_resource
|
||||
* just like you were using the resource itself.
|
||||
*
|
||||
* @ret the underlying resource
|
||||
*/
|
||||
operator resource_type() const { return resource; }
|
||||
|
||||
/**
|
||||
* This function provides explicit access to the resource. Its behaviour
|
||||
* is identical to operator resource_type()
|
||||
*
|
||||
* @ret the underlying resource
|
||||
*/
|
||||
resource_type get() const { return resource; }
|
||||
|
||||
/**
|
||||
* This function provides convenient direct access to the -> operator
|
||||
* if the underlying resource is a pointer. Only call this function
|
||||
* if resource_type is a pointer type.
|
||||
*/
|
||||
resource_type operator->() const { return resource; }
|
||||
|
||||
resource_type& assign(const resource_type& o) {
|
||||
release(resource);
|
||||
resource = o;
|
||||
|
||||
return resource;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A helper policy for scoped_ptr.
|
||||
* It will call the delete operator on a pointer, and assign the pointer to 0
|
||||
*/
|
||||
struct delete_item {
|
||||
template<typename T>
|
||||
void operator()(T*& p) const { delete p; p = 0; }
|
||||
};
|
||||
/**
|
||||
* A helper policy for scoped_array.
|
||||
* It will call the delete[] operator on a pointer, and assign the pointer to 0
|
||||
*/
|
||||
struct delete_array {
|
||||
template<typename T>
|
||||
void operator()(T*& p) const { delete [] p; p = 0; }
|
||||
};
|
||||
|
||||
/**
|
||||
* A class which implements an approximation of
|
||||
* template<typename T>
|
||||
* typedef scoped_resource<T*,delete_item> scoped_ptr<T>;
|
||||
*
|
||||
* It is a convenient synonym for a common usage of @ref scoped_resource.
|
||||
* See scoped_resource for more details on how this class behaves.
|
||||
*
|
||||
* Usage example:
|
||||
* @code
|
||||
* {
|
||||
* const scoped_ptr<Object> ptr(new Object);
|
||||
* ...use ptr as you would a normal Object*...
|
||||
* } // ptr is automatically deleted here
|
||||
* @endcode
|
||||
*
|
||||
* NOTE: use this class only to manage a single object, *never* an array.
|
||||
* Use scoped_array to manage arrays. This distinction is because you
|
||||
* may call delete only on objects allocated with new, delete[] only
|
||||
* on objects allocated with new[].
|
||||
*/
|
||||
template<typename T>
|
||||
struct scoped_ptr : public scoped_resource<T*,delete_item>
|
||||
{
|
||||
explicit scoped_ptr(T* p) : scoped_resource<T*,delete_item>(p) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* This class has identical behaviour to @ref scoped_ptr, except it manages
|
||||
* heap-allocated arrays instead of heap-allocated single objects
|
||||
*
|
||||
* Usage example:
|
||||
* @code
|
||||
* {
|
||||
* const scoped_array<char> ptr(new char[n]);
|
||||
* ...use ptr as you would a normal char*...
|
||||
* } // ptr is automatically deleted here
|
||||
* @endcode
|
||||
*
|
||||
*/
|
||||
template<typename T>
|
||||
struct scoped_array : public scoped_resource<T*,delete_array>
|
||||
{
|
||||
explicit scoped_array(T* p) : scoped_resource<T*,delete_array>(p) {}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
69
src/sdl_utils.cpp
Normal file
69
src/sdl_utils.cpp
Normal file
|
@ -0,0 +1,69 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "sdl_utils.hpp"
|
||||
|
||||
SDL_Surface* scale_surface(SDL_Surface* surface, int w, int h)
|
||||
{
|
||||
SDL_Surface* const dest = SDL_CreateRGBSurface(SDL_SWSURFACE,w,h,
|
||||
surface->format->BitsPerPixel,
|
||||
surface->format->Rmask,
|
||||
surface->format->Gmask,
|
||||
surface->format->Bmask,
|
||||
surface->format->Amask);
|
||||
if(dest == NULL)
|
||||
return NULL;
|
||||
|
||||
const double xratio = static_cast<double>(surface->w)/
|
||||
static_cast<double>(w);
|
||||
const double yratio = static_cast<double>(surface->h)/
|
||||
static_cast<double>(h);
|
||||
|
||||
const int srcxpad = (surface->w%2) == 1 ? 1:0;
|
||||
const int dstxpad = (dest->w%2) == 1 ? 1:0;
|
||||
|
||||
double ysrc = 0.0;
|
||||
for(int ydst = 0; ydst != h; ++ydst, ysrc += yratio) {
|
||||
double xsrc = 0.0;
|
||||
for(int xdst = 0; xdst != w; ++xdst, xsrc += xratio) {
|
||||
int xsrcint = static_cast<int>(xsrc);
|
||||
const int ysrcint = static_cast<int>(ysrc);
|
||||
|
||||
xsrcint += srcxpad*ysrcint;
|
||||
|
||||
const int dstpad = dstxpad*ydst;
|
||||
|
||||
reinterpret_cast<short*>(dest->pixels)[ydst*w + xdst + dstpad] =
|
||||
reinterpret_cast<short*>(surface->pixels)[
|
||||
ysrcint*surface->w + xsrcint];
|
||||
}
|
||||
}
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
SDL_Surface* get_surface_portion(SDL_Surface* src, SDL_Rect& area)
|
||||
{
|
||||
if(area.x + area.w >= src->w || area.y + area.h >= src->h)
|
||||
return NULL;
|
||||
|
||||
const SDL_PixelFormat* const fmt = src->format;
|
||||
SDL_Surface* const dst = SDL_CreateRGBSurface(0,area.w,area.h,
|
||||
fmt->BitsPerPixel,fmt->Rmask,
|
||||
fmt->Gmask,fmt->Bmask,
|
||||
fmt->Amask);
|
||||
SDL_Rect dstarea = {0,0,0,0};
|
||||
|
||||
SDL_BlitSurface(src,&area,dst,&dstarea);
|
||||
|
||||
return dst;
|
||||
}
|
84
src/sdl_utils.hpp
Normal file
84
src/sdl_utils.hpp
Normal file
|
@ -0,0 +1,84 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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 SDL_UTILS_INCLUDED
|
||||
#define SDL_UTILS_INCLUDED
|
||||
|
||||
#include "config.hpp"
|
||||
#include "scoped_resource.hpp"
|
||||
|
||||
#include "SDL.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
|
||||
struct free_sdl_surface {
|
||||
void operator()(SDL_Surface* surface) const { SDL_FreeSurface(surface); }
|
||||
};
|
||||
|
||||
typedef util::scoped_resource<SDL_Surface*,free_sdl_surface> scoped_sdl_surface;
|
||||
|
||||
SDL_Surface* scale_surface(SDL_Surface* surface, int w, int h);
|
||||
|
||||
SDL_Surface* get_surface_portion(SDL_Surface* src, SDL_Rect& rect);
|
||||
|
||||
struct pixel_data
|
||||
{
|
||||
pixel_data() : r(0), g(0), b(0)
|
||||
{}
|
||||
|
||||
pixel_data(int red, int green, int blue) : r(red), g(green), b(blue)
|
||||
{}
|
||||
|
||||
pixel_data(int pixel, SDL_PixelFormat* fmt) {
|
||||
unformat(pixel, fmt);
|
||||
}
|
||||
|
||||
pixel_data(config& cfg) {
|
||||
read(cfg);
|
||||
}
|
||||
|
||||
int format(SDL_PixelFormat* fmt) const {
|
||||
return SDL_MapRGB(fmt,r,g,b);
|
||||
}
|
||||
|
||||
void unformat(int pixel, SDL_PixelFormat* fmt) {
|
||||
r = ((pixel&fmt->Rmask) >> fmt->Rshift);
|
||||
g = ((pixel&fmt->Gmask) >> fmt->Gshift);
|
||||
b = ((pixel&fmt->Bmask) >> fmt->Bshift);
|
||||
}
|
||||
|
||||
void read(config& cfg) {
|
||||
const std::string& red = cfg.values["red"];
|
||||
const std::string& green = cfg.values["green"];
|
||||
const std::string& blue = cfg.values["blue"];
|
||||
|
||||
if(red.empty())
|
||||
r = 0;
|
||||
else
|
||||
r = atoi(red.c_str());
|
||||
|
||||
if(green.empty())
|
||||
g = 0;
|
||||
else
|
||||
g = atoi(green.c_str());
|
||||
|
||||
if(blue.empty())
|
||||
b = 0;
|
||||
else
|
||||
b = atoi(blue.c_str());
|
||||
}
|
||||
|
||||
int r, g, b;
|
||||
};
|
||||
|
||||
#endif
|
196
src/sound.cpp
Normal file
196
src/sound.cpp
Normal file
|
@ -0,0 +1,196 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "sound.hpp"
|
||||
|
||||
#include "SDL_mixer.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
|
||||
namespace {
|
||||
|
||||
bool mix_ok = false;
|
||||
std::map<std::string,Mix_Chunk*> sound_cache;
|
||||
std::map<std::string,Mix_Music*> music_cache;
|
||||
|
||||
std::string current_music = "";
|
||||
|
||||
bool music_off = false;
|
||||
bool sound_off = false;
|
||||
|
||||
}
|
||||
|
||||
namespace sound {
|
||||
|
||||
manager::manager()
|
||||
{
|
||||
const int res =
|
||||
Mix_OpenAudio(MIX_DEFAULT_FREQUENCY,MIX_DEFAULT_FORMAT,2,1024);
|
||||
if(res >= 0) {
|
||||
mix_ok = true;
|
||||
Mix_AllocateChannels(8);
|
||||
} else {
|
||||
mix_ok = false;
|
||||
std::cerr << "Could not initialize audio: " << SDL_GetError() << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
manager::~manager()
|
||||
{
|
||||
if(!mix_ok)
|
||||
return;
|
||||
|
||||
Mix_HaltMusic();
|
||||
Mix_HaltChannel(-1);
|
||||
|
||||
for(std::map<std::string,Mix_Chunk*>::iterator i = sound_cache.begin();
|
||||
i != sound_cache.end(); ++i) {
|
||||
Mix_FreeChunk(i->second);
|
||||
}
|
||||
|
||||
for(std::map<std::string,Mix_Music*>::iterator j = music_cache.begin();
|
||||
j != music_cache.end(); ++j) {
|
||||
Mix_FreeMusic(j->second);
|
||||
}
|
||||
|
||||
Mix_CloseAudio();
|
||||
}
|
||||
|
||||
void play_music(const std::string& file)
|
||||
{
|
||||
if(!mix_ok || current_music == file)
|
||||
return;
|
||||
|
||||
if(music_off) {
|
||||
current_music = file;
|
||||
return;
|
||||
}
|
||||
|
||||
std::map<std::string,Mix_Music*>::const_iterator itor =
|
||||
music_cache.find(file);
|
||||
if(itor == music_cache.end()) {
|
||||
static const std::string music_prefix = "music/";
|
||||
|
||||
std::string filename;
|
||||
Mix_Music* music = NULL;
|
||||
|
||||
#ifdef WESNOTH_PATH
|
||||
filename = WESNOTH_PATH + std::string("/") + music_prefix + file;
|
||||
music = Mix_LoadMUS(filename.c_str());
|
||||
#endif
|
||||
|
||||
if(music == NULL) {
|
||||
filename = music_prefix + file;
|
||||
music = Mix_LoadMUS(filename.c_str());
|
||||
}
|
||||
|
||||
if(music == NULL) {
|
||||
std::cerr << "Could not load music file '" << filename << "': "
|
||||
<< SDL_GetError() << "\n";
|
||||
return;
|
||||
}
|
||||
|
||||
itor = music_cache.insert(std::pair<std::string,Mix_Music*>(
|
||||
file,music)).first;
|
||||
}
|
||||
|
||||
if(Mix_PlayingMusic()) {
|
||||
Mix_FadeOutMusic(500);
|
||||
}
|
||||
|
||||
const int res = Mix_FadeInMusic(itor->second,-1,500);
|
||||
if(res < 0) {
|
||||
std::cerr << "Could not play music: " << SDL_GetError() << "\n";
|
||||
}
|
||||
|
||||
current_music = file;
|
||||
}
|
||||
|
||||
void play_sound(const std::string& file)
|
||||
{
|
||||
if(!mix_ok || sound_off)
|
||||
return;
|
||||
|
||||
std::map<std::string,Mix_Chunk*>::const_iterator itor =
|
||||
sound_cache.find(file);
|
||||
if(itor == sound_cache.end()) {
|
||||
static const std::string sound_prefix = "sounds/";
|
||||
std::string filename;
|
||||
Mix_Chunk* sfx = NULL;
|
||||
|
||||
#ifdef WESNOTH_PATH
|
||||
filename = WESNOTH_PATH + std::string("/") + sound_prefix + file;
|
||||
sfx = Mix_LoadWAV(filename.c_str());
|
||||
#endif
|
||||
|
||||
if(sfx == NULL) {
|
||||
filename = sound_prefix + file;
|
||||
sfx = Mix_LoadWAV(filename.c_str());
|
||||
}
|
||||
|
||||
if(sfx == NULL) {
|
||||
std::cerr << "Could not load sound file '" << filename << "': "
|
||||
<< SDL_GetError() << "\n";
|
||||
return;
|
||||
}
|
||||
|
||||
itor = sound_cache.insert(std::pair<std::string,Mix_Chunk*>(
|
||||
file,sfx)).first;
|
||||
}
|
||||
|
||||
//play on the first available channel
|
||||
const int res = Mix_PlayChannel(-1,itor->second,0);
|
||||
if(res < 0) {
|
||||
std::cerr << "error playing sound effect: " << SDL_GetError() << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
void set_music_volume(double vol)
|
||||
{
|
||||
if(!mix_ok)
|
||||
return;
|
||||
|
||||
if(vol < 0.05) {
|
||||
Mix_HaltMusic();
|
||||
music_off = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Mix_VolumeMusic(int(vol*double(MIX_MAX_VOLUME)));
|
||||
|
||||
//if the music was off completely, start playing it again now
|
||||
if(music_off) {
|
||||
music_off = false;
|
||||
const std::string music = current_music;
|
||||
current_music = "";
|
||||
if(!music.empty()) {
|
||||
play_music(music);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void set_sound_volume(double vol)
|
||||
{
|
||||
if(!mix_ok)
|
||||
return;
|
||||
|
||||
if(vol < 0.05) {
|
||||
sound_off = true;
|
||||
return;
|
||||
} else {
|
||||
sound_off = false;
|
||||
Mix_Volume(-1,int(vol*double(MIX_MAX_VOLUME)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
33
src/sound.hpp
Normal file
33
src/sound.hpp
Normal file
|
@ -0,0 +1,33 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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 SOUND_HPP_INCLUDED
|
||||
#define SOUND_HPP_INCLUDED
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace sound {
|
||||
|
||||
struct manager {
|
||||
manager();
|
||||
~manager();
|
||||
};
|
||||
|
||||
void play_music(const std::string& file);
|
||||
void play_sound(const std::string& file);
|
||||
|
||||
void set_music_volume(double vol);
|
||||
void set_sound_volume(double vol);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
178
src/team.cpp
Normal file
178
src/team.cpp
Normal file
|
@ -0,0 +1,178 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "game_config.hpp"
|
||||
#include "replay.hpp"
|
||||
#include "team.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
|
||||
team::target::target(config& cfg)
|
||||
: criteria(cfg), value(atof(cfg.values["value"].c_str()))
|
||||
{
|
||||
}
|
||||
|
||||
team::team_info::team_info(config& cfg)
|
||||
{
|
||||
gold = cfg.values["gold"];
|
||||
name = cfg.values["name"];
|
||||
aggression = atof(cfg.values["aggression"].c_str());
|
||||
if(aggression == 0.0)
|
||||
aggression = 0.5;
|
||||
|
||||
const std::string& enemies_list = cfg.values["enemy"];
|
||||
if(!enemies_list.empty()) {
|
||||
std::vector<std::string> venemies = config::split(enemies_list);
|
||||
for(std::vector<std::string>::const_iterator i = venemies.begin();
|
||||
i != venemies.end(); ++i) {
|
||||
enemies.push_back(atoi(i->c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
if(cfg.values["controller"] == "human")
|
||||
human = true;
|
||||
else
|
||||
human = false;
|
||||
|
||||
const std::string& leader_val = cfg.values["leader_value"];
|
||||
if(leader_val.empty()) {
|
||||
leader_value = 3.0;
|
||||
} else {
|
||||
leader_value = atof(leader_val.c_str());
|
||||
}
|
||||
|
||||
const std::string& village_val = cfg.values["village_value"];
|
||||
if(village_val.empty()) {
|
||||
village_value = 1.0;
|
||||
} else {
|
||||
village_value = atof(village_val.c_str());
|
||||
}
|
||||
|
||||
std::vector<std::string> recruits = config::split(cfg.values["recruit"]);
|
||||
for(std::vector<std::string>::const_iterator i = recruits.begin();
|
||||
i != recruits.end(); ++i) {
|
||||
can_recruit.insert(*i);
|
||||
}
|
||||
|
||||
recruitment_pattern = config::split(cfg.values["recruitment_pattern"]);
|
||||
|
||||
//default recruitment pattern is to buy 2 fighters for every 1 archer
|
||||
if(recruitment_pattern.empty()) {
|
||||
recruitment_pattern.push_back("fighter");
|
||||
recruitment_pattern.push_back("fighter");
|
||||
recruitment_pattern.push_back("archer");
|
||||
}
|
||||
|
||||
//additional targets
|
||||
std::vector<config*>& tgts = cfg.children["target"];
|
||||
for(std::vector<config*>::iterator tgt = tgts.begin();
|
||||
tgt != tgts.end(); ++tgt) {
|
||||
targets.push_back(target(**tgt));
|
||||
}
|
||||
}
|
||||
|
||||
team::team(config& cfg, int gold) : gold_(gold), info_(cfg)
|
||||
{
|
||||
if(info_.gold.empty() == false)
|
||||
gold_ = ::atoi(info_.gold.c_str());
|
||||
}
|
||||
|
||||
void team::get_tower(const gamemap::location& loc)
|
||||
{
|
||||
towers_.insert(loc);
|
||||
}
|
||||
|
||||
void team::lose_tower(const gamemap::location& loc)
|
||||
{
|
||||
towers_.erase(towers_.find(loc));
|
||||
}
|
||||
|
||||
int team::towers() const
|
||||
{
|
||||
return towers_.size();
|
||||
}
|
||||
|
||||
bool team::owns_tower(const gamemap::location& loc) const
|
||||
{
|
||||
return towers_.count(loc) > 0;
|
||||
}
|
||||
|
||||
int team::gold() const
|
||||
{
|
||||
return gold_;
|
||||
}
|
||||
|
||||
int team::income() const
|
||||
{
|
||||
return towers_.size()*game_config::tower_income+game_config::base_income;
|
||||
}
|
||||
|
||||
void team::new_turn()
|
||||
{
|
||||
gold_ += income();
|
||||
}
|
||||
|
||||
void team::spend_gold(int amount)
|
||||
{
|
||||
gold_ -= amount;
|
||||
}
|
||||
|
||||
const std::set<std::string>& team::recruits() const
|
||||
{
|
||||
return info_.can_recruit;
|
||||
}
|
||||
|
||||
const std::vector<std::string>& team::recruitment_pattern() const
|
||||
{
|
||||
return info_.recruitment_pattern;
|
||||
}
|
||||
|
||||
const std::string& team::name() const
|
||||
{
|
||||
return info_.name;
|
||||
}
|
||||
|
||||
bool team::is_enemy(int n) const
|
||||
{
|
||||
//if enemies aren't listed, then everyone is an enemy
|
||||
if(info_.enemies.empty())
|
||||
return true;
|
||||
|
||||
return std::find(info_.enemies.begin(),info_.enemies.end(),n) !=
|
||||
info_.enemies.end();
|
||||
}
|
||||
|
||||
double team::aggression() const
|
||||
{
|
||||
return info_.aggression;
|
||||
}
|
||||
|
||||
bool team::is_human() const
|
||||
{
|
||||
return info_.human;
|
||||
}
|
||||
|
||||
double team::leader_value() const
|
||||
{
|
||||
return info_.leader_value;
|
||||
}
|
||||
|
||||
double team::village_value() const
|
||||
{
|
||||
return info_.village_value;
|
||||
}
|
||||
|
||||
std::vector<team::target>& team::targets()
|
||||
{
|
||||
return info_.targets;
|
||||
}
|
81
src/team.hpp
Normal file
81
src/team.hpp
Normal file
|
@ -0,0 +1,81 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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 TEAM_H_INCLUDED
|
||||
#define TEAM_H_INCLUDED
|
||||
|
||||
#include "config.hpp"
|
||||
#include "map.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class team
|
||||
{
|
||||
public:
|
||||
|
||||
struct target {
|
||||
explicit target(config& cfg);
|
||||
config criteria;
|
||||
double value;
|
||||
};
|
||||
|
||||
struct team_info
|
||||
{
|
||||
team_info(config& cfg);
|
||||
std::string name;
|
||||
std::string gold;
|
||||
std::set<std::string> can_recruit;
|
||||
std::vector<std::string> recruitment_pattern;
|
||||
double aggression;
|
||||
std::vector<int> enemies;
|
||||
bool human;
|
||||
|
||||
double leader_value, village_value;
|
||||
|
||||
std::vector<target> targets;
|
||||
};
|
||||
|
||||
team(config& cfg, int gold=100);
|
||||
void get_tower(const gamemap::location&);
|
||||
void lose_tower(const gamemap::location&);
|
||||
int towers() const;
|
||||
bool owns_tower(const gamemap::location&) const;
|
||||
|
||||
int gold() const;
|
||||
int income() const;
|
||||
void new_turn();
|
||||
void spend_gold(int amount);
|
||||
|
||||
const std::set<std::string>& recruits() const;
|
||||
const std::vector<std::string>& recruitment_pattern() const;
|
||||
const std::string& name() const;
|
||||
|
||||
bool is_enemy(int side) const;
|
||||
double aggression() const;
|
||||
|
||||
bool is_human() const;
|
||||
|
||||
double leader_value() const;
|
||||
double village_value() const;
|
||||
|
||||
std::vector<target>& targets();
|
||||
private:
|
||||
int gold_;
|
||||
std::set<gamemap::location> towers_;
|
||||
|
||||
team_info info_;
|
||||
};
|
||||
|
||||
#endif
|
81
src/terrain.cpp
Normal file
81
src/terrain.cpp
Normal file
|
@ -0,0 +1,81 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "terrain.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
|
||||
terrain_type::terrain_type() : image_("void"), type_(' '), letter_(' ')
|
||||
{}
|
||||
|
||||
terrain_type::terrain_type(config& cfg)
|
||||
{
|
||||
image_ = cfg.values["image"];
|
||||
name_ = cfg.values["name"];
|
||||
const std::string& letter = cfg.values["char"];
|
||||
assert(!letter.empty());
|
||||
letter_ = letter[0];
|
||||
|
||||
const std::string& alias = cfg.values["aliasof"];
|
||||
if(alias.empty())
|
||||
type_ = letter_;
|
||||
else
|
||||
type_ = alias[0];
|
||||
|
||||
colour_.read(cfg);
|
||||
}
|
||||
|
||||
const std::string& terrain_type::image() const
|
||||
{
|
||||
return image_;
|
||||
}
|
||||
|
||||
const std::string& terrain_type::name() const
|
||||
{
|
||||
return name_;
|
||||
}
|
||||
|
||||
char terrain_type::letter() const
|
||||
{
|
||||
return letter_;
|
||||
}
|
||||
|
||||
char terrain_type::type() const
|
||||
{
|
||||
return type_;
|
||||
}
|
||||
|
||||
pixel_data terrain_type::get_rgb() const
|
||||
{
|
||||
return colour_;
|
||||
}
|
||||
|
||||
bool terrain_type::is_alias() const
|
||||
{
|
||||
return type_ != letter_;
|
||||
}
|
||||
|
||||
void create_terrain_maps(std::vector<config*>& cfgs,
|
||||
std::vector<char>& terrain_precedence,
|
||||
std::map<char,terrain_type>& letter_to_terrain,
|
||||
std::map<std::string,terrain_type>& str_to_terrain)
|
||||
{
|
||||
for(std::vector<config*>::iterator i = cfgs.begin(); i != cfgs.end(); ++i) {
|
||||
terrain_type terrain(**i);
|
||||
terrain_precedence.push_back(terrain.letter());
|
||||
letter_to_terrain.insert(std::pair<char,terrain_type>(
|
||||
terrain.letter(),terrain));
|
||||
str_to_terrain.insert(std::pair<std::string,terrain_type>(
|
||||
terrain.name(),terrain));
|
||||
}
|
||||
}
|
52
src/terrain.hpp
Normal file
52
src/terrain.hpp
Normal file
|
@ -0,0 +1,52 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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 TERRAIN_H_INCLUDED
|
||||
#define TERRAIN_H_INCLUDED
|
||||
|
||||
#include "config.hpp"
|
||||
#include "sdl_utils.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
class terrain_type
|
||||
{
|
||||
public:
|
||||
terrain_type();
|
||||
terrain_type(config& cfg);
|
||||
|
||||
const std::string& image() const;
|
||||
const std::string& name() const;
|
||||
char letter() const;
|
||||
char type() const;
|
||||
|
||||
pixel_data get_rgb() const;
|
||||
|
||||
bool is_alias() const;
|
||||
private:
|
||||
std::string image_, name_;
|
||||
|
||||
//the 'letter' is the letter that represents this
|
||||
//terrain type. The 'type' is the letter of the
|
||||
//terrain type which this is equivalent to, which
|
||||
//may be the same as 'letter'
|
||||
char type_, letter_;
|
||||
|
||||
pixel_data colour_;
|
||||
};
|
||||
|
||||
void create_terrain_maps(std::vector<config*>& cfgs,
|
||||
std::vector<char>& terrain_precedence,
|
||||
std::map<char,terrain_type>& letter_to_terrain,
|
||||
std::map<std::string,terrain_type>& str_to_terrain);
|
||||
|
||||
#endif
|
641
src/unit.cpp
Normal file
641
src/unit.cpp
Normal file
|
@ -0,0 +1,641 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "game_config.hpp"
|
||||
#include "gamestatus.hpp"
|
||||
#include "replay.hpp"
|
||||
#include "unit.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
namespace {
|
||||
const std::string ModificationTypes[] = { "object", "trait" };
|
||||
const int NumModificationTypes = sizeof(ModificationTypes)/
|
||||
sizeof(*ModificationTypes);
|
||||
}
|
||||
|
||||
bool compare_unit_values::operator()(const unit& a, const unit& b) const
|
||||
{
|
||||
const int lvla = a.type().level();
|
||||
const int lvlb = b.type().level();
|
||||
|
||||
const std::string& namea = a.type().name();
|
||||
const std::string& nameb = b.type().name();
|
||||
|
||||
const int xpa = a.max_experience() - a.experience();
|
||||
const int xpb = b.max_experience() - b.experience();
|
||||
|
||||
return lvla > lvlb || lvla == lvlb && namea < nameb ||
|
||||
lvla == lvlb && namea == nameb && xpa < xpb;
|
||||
}
|
||||
|
||||
//constructor for reading a unit
|
||||
unit::unit(game_data& data, config& cfg) : moves_(0), facingLeft_(true),
|
||||
recruit_(false),
|
||||
state_(STATE_NORMAL),
|
||||
guardian_(false)
|
||||
{
|
||||
read(data,cfg);
|
||||
}
|
||||
|
||||
//constructor for creating a new unit
|
||||
unit::unit(const unit_type* t, int side, bool use_traits) :
|
||||
type_(t), facingLeft_(side != 1), state_(STATE_NORMAL),
|
||||
hitpoints_(t->hitpoints()),
|
||||
experience_(0), side_(side), moves_(0),
|
||||
recruit_(false), attacks_(t->attacks()),
|
||||
backupAttacks_(t->attacks()),
|
||||
maxHitpoints_(t->hitpoints()),
|
||||
backupMaxHitpoints_(t->hitpoints()),
|
||||
maxMovement_(t->movement()),
|
||||
backupMaxMovement_(t->movement()),
|
||||
maxExperience_(t->experience_needed()),
|
||||
backupMaxExperience_(t->experience_needed()),
|
||||
guardian_(false)
|
||||
{
|
||||
//calculate the unit's traits
|
||||
std::vector<config*> traits = t->possible_traits();
|
||||
const int num_traits = 2;
|
||||
if(use_traits && traits.size() >= num_traits) {
|
||||
std::set<int> chosen_traits;
|
||||
for(int i = 0; i != num_traits; ++i) {
|
||||
int num = recorder.get_random()%(traits.size()-i);
|
||||
while(chosen_traits.count(num)) {
|
||||
++num;
|
||||
}
|
||||
|
||||
chosen_traits.insert(num);
|
||||
|
||||
add_modification("trait",*traits[num]);
|
||||
|
||||
}
|
||||
|
||||
//build the traits description, making sure the traits are always
|
||||
//in the same order.
|
||||
for(std::set<int>::const_iterator itor = chosen_traits.begin();
|
||||
itor != chosen_traits.end(); ++itor) {
|
||||
traitsDescription_ += traits[*itor]->values["name"];
|
||||
traitsDescription_ += ",";
|
||||
}
|
||||
|
||||
//get rid of the trailing comma
|
||||
if(!traitsDescription_.empty())
|
||||
traitsDescription_.resize(traitsDescription_.size()-1);
|
||||
}
|
||||
}
|
||||
|
||||
//constructor for advancing a unit from a lower level
|
||||
unit::unit(const unit_type* t, const unit& u) :
|
||||
type_(t), facingLeft_(u.facingLeft_), state_(STATE_NORMAL),
|
||||
hitpoints_(t->hitpoints()),
|
||||
experience_(0), side_(u.side()), moves_(u.moves_),
|
||||
recruit_(u.recruit_), description_(u.description_),
|
||||
role_(u.role_), statusFlags_(u.statusFlags_),
|
||||
attacks_(t->attacks()), backupAttacks_(t->attacks()),
|
||||
maxHitpoints_(t->hitpoints()),
|
||||
backupMaxHitpoints_(t->hitpoints()),
|
||||
maxMovement_(t->movement()),
|
||||
backupMaxMovement_(t->movement()),
|
||||
maxExperience_(t->experience_needed()),
|
||||
backupMaxExperience_(t->experience_needed()),
|
||||
modifications_(u.modifications_),
|
||||
traitsDescription_(u.traitsDescription_),
|
||||
guardian_(false)
|
||||
{
|
||||
//apply modifications etc, refresh the unit
|
||||
new_level();
|
||||
}
|
||||
|
||||
const unit_type& unit::type() const
|
||||
{
|
||||
return *type_;
|
||||
}
|
||||
|
||||
std::string unit::name() const
|
||||
{
|
||||
if(description_.empty() == false)
|
||||
return description_;
|
||||
else
|
||||
return type().language_name();
|
||||
}
|
||||
|
||||
const std::string& unit::description() const
|
||||
{
|
||||
return description_;
|
||||
}
|
||||
|
||||
int unit::side() const
|
||||
{
|
||||
return side_;
|
||||
}
|
||||
|
||||
double unit::alpha() const
|
||||
{
|
||||
return type().alpha();
|
||||
}
|
||||
|
||||
void unit::make_recruiter()
|
||||
{
|
||||
recruit_ = true;
|
||||
}
|
||||
|
||||
bool unit::can_recruit() const
|
||||
{
|
||||
return recruit_;
|
||||
}
|
||||
|
||||
int unit::total_movement() const
|
||||
{
|
||||
return maxMovement_;
|
||||
}
|
||||
|
||||
int unit::movement_left() const
|
||||
{
|
||||
return moves_ < 0 ? 0 : moves_;
|
||||
}
|
||||
|
||||
bool unit::can_attack() const
|
||||
{
|
||||
return moves_ != -1;
|
||||
}
|
||||
|
||||
void unit::set_movement(int moves)
|
||||
{
|
||||
if(moves_ != -1)
|
||||
moves_ = moves;
|
||||
}
|
||||
|
||||
void unit::set_attacked()
|
||||
{
|
||||
moves_ = -1;
|
||||
}
|
||||
|
||||
void unit::new_turn()
|
||||
{
|
||||
moves_ = total_movement();
|
||||
if(type().has_ability("ambush"))
|
||||
set_flag("ambush");
|
||||
}
|
||||
|
||||
void unit::end_turn()
|
||||
{
|
||||
remove_flag("slowed");
|
||||
}
|
||||
|
||||
void unit::new_level()
|
||||
{
|
||||
//revert stats to the beginning of the level
|
||||
attacks_ = backupAttacks_;
|
||||
maxHitpoints_ = backupMaxHitpoints_;
|
||||
maxMovement_ = backupMaxMovement_;
|
||||
maxExperience_ = backupMaxExperience_;
|
||||
|
||||
//reapply all permanent modifications
|
||||
apply_modifications();
|
||||
|
||||
heal_all();
|
||||
statusFlags_.clear();
|
||||
}
|
||||
|
||||
int unit::hitpoints() const
|
||||
{
|
||||
return hitpoints_;
|
||||
}
|
||||
|
||||
int unit::max_hitpoints() const
|
||||
{
|
||||
return maxHitpoints_;
|
||||
}
|
||||
|
||||
int unit::experience() const
|
||||
{
|
||||
return experience_;
|
||||
}
|
||||
|
||||
int unit::max_experience() const
|
||||
{
|
||||
return maxExperience_;
|
||||
}
|
||||
|
||||
bool unit::get_experience(int xp)
|
||||
{
|
||||
experience_ += xp;
|
||||
if(experience_ > max_experience())
|
||||
experience_ = max_experience();
|
||||
return advances();
|
||||
}
|
||||
|
||||
bool unit::advances() const
|
||||
{
|
||||
return experience_ >= max_experience() && !type().advances_to().empty();
|
||||
}
|
||||
|
||||
bool unit::gets_hit(int damage)
|
||||
{
|
||||
hitpoints_ -= damage;
|
||||
if(hitpoints_ > max_hitpoints() && damage > 0)
|
||||
hitpoints_ = max_hitpoints();
|
||||
return hitpoints_ <= 0;
|
||||
}
|
||||
|
||||
void unit::heal()
|
||||
{
|
||||
heal(game_config::heal_amount);
|
||||
}
|
||||
|
||||
void unit::heal(int amount)
|
||||
{
|
||||
if(hitpoints_ < max_hitpoints()) {
|
||||
hitpoints_ += amount;
|
||||
if(hitpoints_ > max_hitpoints())
|
||||
hitpoints_ = max_hitpoints();
|
||||
}
|
||||
}
|
||||
|
||||
void unit::heal_all()
|
||||
{
|
||||
hitpoints_ = max_hitpoints();
|
||||
}
|
||||
|
||||
bool unit::invisible(gamemap::TERRAIN terrain) const
|
||||
{
|
||||
static const std::string forest_invisible("ambush");
|
||||
if(terrain == gamemap::FOREST && has_flag(forest_invisible)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool unit::matches_filter(config& cfg) const
|
||||
{
|
||||
const std::string& description = cfg.values["description"];
|
||||
const std::string& type = cfg.values["type"];
|
||||
const std::string& ability = cfg.values["ability"];
|
||||
const std::string& side = cfg.values["side"];
|
||||
const std::string& weapon = cfg.values["has_weapon"];
|
||||
const std::string& role = cfg.values["role"];
|
||||
|
||||
if(description.empty() == false && description != this->description()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string& this_type = this->type().name();
|
||||
|
||||
//the type could be a comma-seperated list of types
|
||||
if(type.empty() == false && type != this_type) {
|
||||
|
||||
//we only do the full CSV search if we find a comma in there,
|
||||
//and if the subsequence is found within the main sequence. This
|
||||
//is because doing the full CSV split is expensive
|
||||
if(std::find(type.begin(),type.end(),',') != type.end() &&
|
||||
std::search(type.begin(),type.end(),this_type.begin(),this_type.end()) !=
|
||||
type.end()) {
|
||||
const std::vector<std::string>& vals = config::split(type);
|
||||
|
||||
if(std::find(vals.begin(),vals.end(),this_type) == vals.end()) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(ability.empty() == false && this->type().has_ability(ability) == false)
|
||||
return false;
|
||||
|
||||
if(side.empty() == false && this->side() != atoi(side.c_str()))
|
||||
return false;
|
||||
|
||||
if(weapon.empty() == false) {
|
||||
bool has_weapon = false;
|
||||
const std::vector<attack_type>& attacks = this->type().attacks();
|
||||
for(std::vector<attack_type>::const_iterator i = attacks.begin();
|
||||
i != attacks.end(); ++i) {
|
||||
if(i->name() == weapon) {
|
||||
has_weapon = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!has_weapon)
|
||||
return false;
|
||||
}
|
||||
|
||||
if(role.empty() == false && role_ != role)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void unit::set_flag(const std::string& flag)
|
||||
{
|
||||
statusFlags_.insert(flag);
|
||||
}
|
||||
|
||||
void unit::remove_flag(const std::string& flag)
|
||||
{
|
||||
statusFlags_.erase(flag);
|
||||
}
|
||||
|
||||
bool unit::has_flag(const std::string& flag) const
|
||||
{
|
||||
return statusFlags_.count(flag) != 0;
|
||||
}
|
||||
|
||||
void unit::read(game_data& data, config& cfg)
|
||||
{
|
||||
std::map<std::string,unit_type>::iterator i = data.unit_types.find(
|
||||
cfg.values["type"]);
|
||||
if(i != data.unit_types.end())
|
||||
type_ = &i->second;
|
||||
else
|
||||
throw gamestatus::load_game_failed("Unit not found: '"
|
||||
+ cfg.values["type"] + "'");
|
||||
|
||||
attacks_ = type_->attacks();
|
||||
backupAttacks_ = attacks_;
|
||||
maxHitpoints_ = type_->hitpoints();
|
||||
backupMaxHitpoints_ = type_->hitpoints();
|
||||
maxMovement_ = type_->movement();
|
||||
backupMaxMovement_ = type_->movement();
|
||||
maxExperience_ = type_->experience_needed();
|
||||
backupMaxExperience_ = type_->experience_needed();
|
||||
|
||||
const std::string& hitpoints = cfg.values["hitpoints"];
|
||||
if(hitpoints.size() == 0)
|
||||
hitpoints_ = type().hitpoints();
|
||||
else
|
||||
hitpoints_ = atoi(hitpoints.c_str());
|
||||
|
||||
const std::string& experience = cfg.values["experience"];
|
||||
if(experience.size() == 0)
|
||||
experience_ = 0;
|
||||
else
|
||||
experience_ = atoi(experience.c_str());
|
||||
|
||||
|
||||
side_ = atoi(cfg.values["side"].c_str());
|
||||
description_ = cfg.values["description"];
|
||||
traitsDescription_ = cfg.values["traits_description"];
|
||||
const std::map<std::string,std::string>::const_iterator recruit_itor =
|
||||
cfg.values.find("canrecruit");
|
||||
if(recruit_itor != cfg.values.end() && recruit_itor->second == "1") {
|
||||
recruit_ = true;
|
||||
}
|
||||
|
||||
const std::vector<config*>& mods = cfg.children["modifications"];
|
||||
if(!mods.empty()) {
|
||||
modifications_ = *mods.front();
|
||||
apply_modifications();
|
||||
}
|
||||
|
||||
const std::string& facing = cfg.values["facing"];
|
||||
if(facing == "reverse")
|
||||
facingLeft_ = false;
|
||||
else
|
||||
facingLeft_ = true;
|
||||
|
||||
const std::string& ai_special = cfg.values["ai_special"];
|
||||
if(ai_special == "guardian") {
|
||||
guardian_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void unit::write(config& cfg) const
|
||||
{
|
||||
cfg.values["type"] = type_->name();
|
||||
|
||||
std::stringstream hp;
|
||||
hp << hitpoints_;
|
||||
cfg.values["hitpoints"] = hp.str();
|
||||
|
||||
std::stringstream xp;
|
||||
xp << experience_;
|
||||
cfg.values["experience"] = xp.str();
|
||||
|
||||
std::stringstream sd;
|
||||
sd << side_;
|
||||
cfg.values["side"] = sd.str();
|
||||
|
||||
cfg.values["description"] = description_;
|
||||
|
||||
cfg.values["traits_description"] = traitsDescription_;
|
||||
|
||||
if(can_recruit())
|
||||
cfg.values["canrecruit"] = "1";
|
||||
|
||||
cfg.children["modifications"].push_back(new config(modifications_));
|
||||
|
||||
cfg.values["facing"] = facingLeft_ ? "normal" : "reverse";
|
||||
}
|
||||
|
||||
void unit::assign_role(const std::string& role)
|
||||
{
|
||||
role_ = role;
|
||||
}
|
||||
|
||||
const std::vector<attack_type>& unit::attacks() const
|
||||
{
|
||||
return attacks_;
|
||||
}
|
||||
|
||||
int unit::movement_cost(const gamemap& map, gamemap::TERRAIN terrain) const
|
||||
{
|
||||
const int res = type_->movement_type().movement_cost(map,terrain);
|
||||
|
||||
static const std::string slowed_string("slowed");
|
||||
if(has_flag(slowed_string)) {
|
||||
return res*2;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
double unit::defense_modifier(const gamemap& map,
|
||||
gamemap::TERRAIN terrain) const
|
||||
{
|
||||
return type_->movement_type().defense_modifier(map,terrain);
|
||||
}
|
||||
|
||||
int unit::damage_against(const attack_type& attack) const
|
||||
{
|
||||
return type_->movement_type().damage_against(attack);
|
||||
}
|
||||
|
||||
const std::string& unit::image() const
|
||||
{
|
||||
switch(state_) {
|
||||
case STATE_NORMAL: return type_->image();
|
||||
case STATE_DEFENDING: return type_->image_defensive();
|
||||
case STATE_ATTACKING: {
|
||||
if(attackType_ == NULL)
|
||||
return type_->image();
|
||||
|
||||
const std::string* const img =
|
||||
attackType_->get_frame(attackingMilliseconds_);
|
||||
|
||||
if(img == NULL)
|
||||
return type_->image();
|
||||
else
|
||||
return *img;
|
||||
}
|
||||
default: return type_->image();
|
||||
}
|
||||
}
|
||||
|
||||
void unit::set_defending(bool newval)
|
||||
{
|
||||
state_ = newval ? STATE_DEFENDING : STATE_NORMAL;
|
||||
}
|
||||
|
||||
void unit::set_attacking(bool newval, const attack_type* type, int ms)
|
||||
{
|
||||
state_ = newval ? STATE_ATTACKING : STATE_NORMAL;
|
||||
attackType_ = type;
|
||||
attackingMilliseconds_ = ms;
|
||||
}
|
||||
|
||||
bool unit::facing_left() const
|
||||
{
|
||||
return facingLeft_;
|
||||
}
|
||||
|
||||
void unit::set_facing_left(bool newval)
|
||||
{
|
||||
facingLeft_ = newval;
|
||||
}
|
||||
|
||||
const std::string& unit::traits_description() const
|
||||
{
|
||||
return traitsDescription_;
|
||||
}
|
||||
|
||||
int unit::value() const
|
||||
{
|
||||
return type().cost();
|
||||
}
|
||||
|
||||
bool unit::is_guardian() const
|
||||
{
|
||||
return guardian_;
|
||||
}
|
||||
|
||||
void unit::add_modification(const std::string& type, config& mod, bool no_add)
|
||||
{
|
||||
const std::string& span = mod.values["duration"];
|
||||
|
||||
if(no_add == false && (span.empty() || span == "forever"))
|
||||
modifications_.children[type].push_back(new config(mod));
|
||||
|
||||
const std::vector<config*>& effects = mod.children["effect"];
|
||||
for(std::vector<config*>::const_iterator i = effects.begin();
|
||||
i != effects.end(); ++i) {
|
||||
|
||||
const std::string& apply_to = (*i)->values["apply_to"];
|
||||
|
||||
if(apply_to == "new_attack") {
|
||||
attacks_.push_back(attack_type(**i));
|
||||
} else if(apply_to == "attack") {
|
||||
for(std::vector<attack_type>::iterator a = attacks_.begin();
|
||||
a != attacks_.end(); ++a) {
|
||||
a->apply_modification(**i);
|
||||
}
|
||||
} else if(apply_to == "hitpoints") {
|
||||
const std::string& increase_hp = (*i)->values["increase"];
|
||||
const std::string& heal_full = (*i)->values["heal_full"];
|
||||
const std::string& increase_total = (*i)->values["increase_total"];
|
||||
const std::string& mult_total = (*i)->values["multiply_total"];
|
||||
|
||||
//if the hitpoints are allowed to end up greater than max hitpoints
|
||||
const std::string& violate_max = (*i)->values["violate_maximum"];
|
||||
|
||||
if(increase_total.empty() == false) {
|
||||
const int increase = atoi(increase_total.c_str());
|
||||
maxHitpoints_ += increase;
|
||||
}
|
||||
|
||||
if(mult_total.empty() == false) {
|
||||
const double factor = atoi(mult_total.c_str());
|
||||
maxHitpoints_ = int(double(maxHitpoints_)*factor);
|
||||
}
|
||||
|
||||
if(maxHitpoints_ < 1)
|
||||
maxHitpoints_ = 1;
|
||||
|
||||
if(heal_full.empty() == false && heal_full != "no") {
|
||||
heal_all();
|
||||
}
|
||||
|
||||
if(increase_hp.empty() == false) {
|
||||
const int increase = atoi(increase_hp.c_str());
|
||||
hitpoints_ += increase;
|
||||
}
|
||||
|
||||
if(hitpoints_ > maxHitpoints_ && violate_max.empty())
|
||||
hitpoints_ = maxHitpoints_;
|
||||
|
||||
if(hitpoints_ < 1)
|
||||
hitpoints_ = 1;
|
||||
} else if(apply_to == "movement") {
|
||||
const std::string& increase = (*i)->values["increase"];
|
||||
const std::string& mult = (*i)->values["multiply"];
|
||||
const std::string& set_to = (*i)->values["set"];
|
||||
|
||||
if(increase.empty() == false) {
|
||||
maxMovement_ += atoi(increase.c_str());
|
||||
if(maxMovement_ < 1)
|
||||
maxMovement_ = 1;
|
||||
}
|
||||
|
||||
if(mult.empty() == false) {
|
||||
maxMovement_ = int(double(maxMovement_)*atof(mult.c_str()));
|
||||
}
|
||||
|
||||
if(set_to.empty() == false) {
|
||||
maxMovement_ = atoi(set_to.c_str());
|
||||
}
|
||||
|
||||
if(moves_ > maxMovement_)
|
||||
moves_ = maxMovement_;
|
||||
} else if(apply_to == "max_experience") {
|
||||
const std::string& increase = (*i)->values["increase"];
|
||||
const std::string& multiply = (*i)->values["multiply"];
|
||||
if(increase.empty() == false) {
|
||||
maxExperience_ += atoi(increase.c_str());
|
||||
}
|
||||
|
||||
if(multiply.empty() == false) {
|
||||
maxExperience_ = int(double(maxExperience_)*
|
||||
atof(multiply.c_str()));
|
||||
}
|
||||
|
||||
if(maxExperience_ < 1) {
|
||||
maxExperience_ = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void unit::apply_modifications()
|
||||
{
|
||||
for(int i = 0; i != NumModificationTypes; ++i) {
|
||||
const std::string& mod = ModificationTypes[i];
|
||||
std::vector<config*>& mods = modifications_.children[mod];
|
||||
for(std::vector<config*>::iterator j = mods.begin();
|
||||
j != mods.end(); ++j) {
|
||||
add_modification(ModificationTypes[i],**j,true);
|
||||
}
|
||||
}
|
||||
}
|
144
src/unit.hpp
Normal file
144
src/unit.hpp
Normal file
|
@ -0,0 +1,144 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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 UNIT_H_INCLUDED
|
||||
#define UNIT_H_INCLUDED
|
||||
|
||||
#include "config.hpp"
|
||||
#include "map.hpp"
|
||||
#include "unit_types.hpp"
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class unit
|
||||
{
|
||||
public:
|
||||
unit(game_data& data, config& cfg);
|
||||
unit(const unit_type* t, int side, bool use_traits=false);
|
||||
|
||||
//a constructor used when advancing a unit
|
||||
unit(const unit_type* t, const unit& u);
|
||||
const unit_type& type() const;
|
||||
std::string name() const;
|
||||
const std::string& description() const;
|
||||
int hitpoints() const;
|
||||
int max_hitpoints() const;
|
||||
int experience() const;
|
||||
int max_experience() const;
|
||||
bool get_experience(int xp);
|
||||
bool advances() const;
|
||||
int side() const;
|
||||
double alpha() const;
|
||||
void make_recruiter();
|
||||
bool can_recruit() const;
|
||||
int total_movement() const;
|
||||
int movement_left() const;
|
||||
bool can_attack() const;
|
||||
void set_movement(int moves);
|
||||
void set_attacked();
|
||||
void new_turn();
|
||||
void end_turn();
|
||||
void new_level();
|
||||
|
||||
bool gets_hit(int damage);
|
||||
void heal();
|
||||
void heal(int amount);
|
||||
void heal_all();
|
||||
|
||||
bool invisible(gamemap::TERRAIN terrain) const;
|
||||
|
||||
bool matches_filter(config& cfg) const;
|
||||
|
||||
void set_flag(const std::string& flag);
|
||||
void remove_flag(const std::string& flag);
|
||||
bool has_flag(const std::string& flag) const;
|
||||
|
||||
void read(game_data& data, config& cfg);
|
||||
|
||||
void write(config& cfg) const;
|
||||
|
||||
void assign_role(const std::string& role);
|
||||
|
||||
const std::vector<attack_type>& attacks() const;
|
||||
|
||||
int movement_cost(const gamemap& map, gamemap::TERRAIN terrain) const;
|
||||
double defense_modifier(const gamemap& map, gamemap::TERRAIN terrain) const;
|
||||
int damage_against(const attack_type& attack) const;
|
||||
|
||||
//gets the unit image that should currently be displayed
|
||||
//(could be in the middle of an attack etc)
|
||||
const std::string& image() const;
|
||||
|
||||
void set_defending(bool newval);
|
||||
void set_attacking(bool newval, const attack_type* type=NULL, int ms=0);
|
||||
|
||||
bool facing_left() const;
|
||||
void set_facing_left(bool newval);
|
||||
|
||||
const std::string& traits_description() const;
|
||||
|
||||
int value() const;
|
||||
bool is_guardian() const;
|
||||
|
||||
void add_modification(const std::string& type, config& modification,
|
||||
bool no_add=false);
|
||||
|
||||
|
||||
private:
|
||||
const unit_type* type_;
|
||||
|
||||
bool facingLeft_;
|
||||
|
||||
enum STATE { STATE_NORMAL, STATE_ATTACKING, STATE_DEFENDING };
|
||||
STATE state_;
|
||||
const attack_type* attackType_;
|
||||
int attackingMilliseconds_;
|
||||
|
||||
int hitpoints_;
|
||||
int maxHitpoints_, backupMaxHitpoints_;
|
||||
int experience_;
|
||||
int maxExperience_, backupMaxExperience_;
|
||||
|
||||
int side_;
|
||||
|
||||
//is set to the number of moves left, and -1 if the unit has attacked
|
||||
int moves_;
|
||||
int maxMovement_, backupMaxMovement_;
|
||||
|
||||
std::string description_;
|
||||
|
||||
bool recruit_;
|
||||
|
||||
std::string role_;
|
||||
|
||||
std::set<std::string> statusFlags_;
|
||||
|
||||
std::vector<attack_type> attacks_;
|
||||
std::vector<attack_type> backupAttacks_;
|
||||
|
||||
config modifications_;
|
||||
|
||||
std::string traitsDescription_;
|
||||
|
||||
bool guardian_;
|
||||
|
||||
void apply_modifications();
|
||||
};
|
||||
|
||||
struct compare_unit_values
|
||||
{
|
||||
bool operator()(const unit& a, const unit& b) const;
|
||||
};
|
||||
|
||||
#endif
|
581
src/unit_types.cpp
Normal file
581
src/unit_types.cpp
Normal file
|
@ -0,0 +1,581 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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.
|
||||
*/
|
||||
#include "game_config.hpp"
|
||||
#include "gamestatus.hpp"
|
||||
#include "language.hpp"
|
||||
#include "unit_types.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
|
||||
//these headers are used to check for file existence
|
||||
#ifdef linux
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
attack_type::attack_type(config& cfg)
|
||||
{
|
||||
name_ = cfg.values["name"];
|
||||
type_ = cfg.values["type"];
|
||||
special_ = cfg.values["special"];
|
||||
range_ = cfg.values["range"] == "long" ? LONG_RANGE : SHORT_RANGE;
|
||||
damage_ = atol(cfg.values["damage"].c_str());
|
||||
num_attacks_ = atol(cfg.values["number"].c_str());
|
||||
|
||||
std::vector<config*>& frames = cfg.children["frame"];
|
||||
std::vector<config*>::iterator i;
|
||||
for(i = frames.begin(); i != frames.end(); ++i){
|
||||
const int beg = atoi((*i)->values["begin"].c_str());
|
||||
const int end = atoi((*i)->values["end"].c_str());
|
||||
const std::string& img = (*i)->values["image"];
|
||||
frames_[UNIT_FRAME].push_back(frame(beg,end,img));
|
||||
}
|
||||
|
||||
std::vector<config*>& missile_frames = cfg.children["missile_frame"];
|
||||
for(i = missile_frames.begin(); i != missile_frames.end(); ++i){
|
||||
const int beg = atoi((*i)->values["begin"].c_str());
|
||||
const int end = atoi((*i)->values["end"].c_str());
|
||||
const std::string& img = (*i)->values["image"];
|
||||
const std::string& img_diag = (*i)->values["image_diagonal"];
|
||||
if(img_diag.empty())
|
||||
frames_[MISSILE_FRAME].push_back(frame(beg,end,img));
|
||||
else
|
||||
frames_[MISSILE_FRAME].push_back(frame(beg,end,img,img_diag));
|
||||
|
||||
}
|
||||
|
||||
std::vector<config*>& sounds = cfg.children["sound"];
|
||||
for(i = sounds.begin(); i != sounds.end(); ++i) {
|
||||
sfx sound;
|
||||
sound.time = atoi((*i)->values["time"].c_str());
|
||||
sound.on_hit = (*i)->values["sound"];
|
||||
sound.on_miss = (*i)->values["sound_miss"];
|
||||
if(sound.on_miss.empty())
|
||||
sound.on_miss = sound.on_hit;
|
||||
|
||||
if(sound.on_miss == "null")
|
||||
sound.on_miss = "";
|
||||
|
||||
sfx_.push_back(sound);
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& attack_type::name() const
|
||||
{
|
||||
return name_;
|
||||
}
|
||||
|
||||
const std::string& attack_type::type() const
|
||||
{
|
||||
return type_;
|
||||
}
|
||||
|
||||
const std::string& attack_type::special() const
|
||||
{
|
||||
return special_;
|
||||
}
|
||||
|
||||
attack_type::RANGE attack_type::range() const
|
||||
{
|
||||
return range_;
|
||||
}
|
||||
|
||||
int attack_type::damage() const
|
||||
{
|
||||
return damage_;
|
||||
}
|
||||
|
||||
int attack_type::num_attacks() const
|
||||
{
|
||||
return num_attacks_;
|
||||
}
|
||||
|
||||
int attack_type::get_first_frame(attack_type::FRAME_TYPE type) const
|
||||
{
|
||||
if(frames_[type].empty())
|
||||
return 0;
|
||||
else
|
||||
return minimum<int>(frames_[type].front().start,0);
|
||||
}
|
||||
|
||||
int attack_type::get_last_frame(attack_type::FRAME_TYPE type) const
|
||||
{
|
||||
if(frames_[type].empty())
|
||||
return 0;
|
||||
else
|
||||
return maximum<int>(frames_[type].back().end,0);
|
||||
}
|
||||
|
||||
const std::string* attack_type::get_frame(int milliseconds,
|
||||
attack_type::FRAME_TYPE type,
|
||||
attack_type::FRAME_DIRECTION dir) const
|
||||
{
|
||||
for(std::vector<frame>::const_iterator i = frames_[type].begin();
|
||||
i != frames_[type].end(); ++i) {
|
||||
if(i->start > milliseconds)
|
||||
return NULL;
|
||||
|
||||
if(i->start <= milliseconds && i->end > milliseconds) {
|
||||
if(dir == DIAGONAL && i->image_diagonal != NULL)
|
||||
return i->image_diagonal;
|
||||
else
|
||||
return i->image;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const std::vector<attack_type::sfx>& attack_type::sound_effects() const
|
||||
{
|
||||
return sfx_;
|
||||
}
|
||||
|
||||
bool attack_type::matches_filter(config& cfg) const
|
||||
{
|
||||
const std::string& filter_range = cfg.values["range"];
|
||||
const std::string& filter_name = cfg.values["name"];
|
||||
const std::string& filter_type = cfg.values["type"];
|
||||
const std::string& filter_special = cfg.values["special"];
|
||||
|
||||
if(filter_range.empty() == false) {
|
||||
if(filter_range == "short" && range() == LONG_RANGE ||
|
||||
filter_range == "long" && range() == SHORT_RANGE) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(filter_name.empty() == false && filter_name != name())
|
||||
return false;
|
||||
|
||||
if(filter_type.empty() == false && filter_type != type())
|
||||
return false;
|
||||
|
||||
if(filter_special.empty() == false && filter_special != special())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void attack_type::apply_modification(config& cfg)
|
||||
{
|
||||
if(!matches_filter(cfg))
|
||||
return;
|
||||
|
||||
const std::string& set_name = cfg.values["set_name"];
|
||||
const std::string& set_type = cfg.values["set_type"];
|
||||
const std::string& set_special = cfg.values["set_special"];
|
||||
const std::string& increase_damage = cfg.values["increase_damage"];
|
||||
const std::string& multiply_damage = cfg.values["multiply_damage"];
|
||||
const std::string& increase_attacks = cfg.values["increase_attacks"];
|
||||
|
||||
if(set_name.empty() == false) {
|
||||
name_ = set_name;
|
||||
}
|
||||
|
||||
if(set_type.empty() == false) {
|
||||
type_ = set_type;
|
||||
}
|
||||
|
||||
if(set_special.empty() == false) {
|
||||
special_ = set_special;
|
||||
}
|
||||
|
||||
if(increase_damage.empty() == false) {
|
||||
const int increase = atoi(increase_damage.c_str());
|
||||
damage_ += increase;
|
||||
if(damage_ < 1)
|
||||
damage_ = 1;
|
||||
}
|
||||
|
||||
if(multiply_damage.empty() == false) {
|
||||
const double multiply = atof(increase_damage.c_str());
|
||||
if(multiply != 0.0) {
|
||||
damage_ = int(double(damage_)*multiply);
|
||||
if(damage_ < 1)
|
||||
damage_ = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if(increase_attacks.empty() == false) {
|
||||
const int increase = atoi(increase_attacks.c_str());
|
||||
num_attacks_ += increase;
|
||||
if(num_attacks_ < 1) {
|
||||
num_attacks_ = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unit_movement_type::unit_movement_type(config& cfg) : cfg_(cfg)
|
||||
{}
|
||||
|
||||
const std::string& unit_movement_type::name() const
|
||||
{
|
||||
return cfg_.values["name"];
|
||||
}
|
||||
|
||||
int unit_movement_type::movement_cost(const gamemap& map,
|
||||
gamemap::TERRAIN terrain) const
|
||||
{
|
||||
const std::map<gamemap::TERRAIN,int>::const_iterator i =
|
||||
moveCosts_.find(terrain);
|
||||
if(i != moveCosts_.end()) {
|
||||
return i->second;
|
||||
}
|
||||
|
||||
const std::vector<config*>& v = cfg_.children["movement costs"];
|
||||
if(v.empty())
|
||||
return 1;
|
||||
|
||||
config* movement_costs = v[0];
|
||||
const std::string& name = map.underlying_terrain_name(terrain);
|
||||
if(terrain == 'b') {
|
||||
std::cout << "underlying terrain: " << name << "\n";
|
||||
}
|
||||
const std::string& val = movement_costs->values[name];
|
||||
int res = atoi(val.c_str());
|
||||
|
||||
//don't allow 0-movement terrain
|
||||
if(res == 0) {
|
||||
res = 100;
|
||||
}
|
||||
|
||||
moveCosts_.insert(std::pair<gamemap::TERRAIN,int>(terrain,res));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
double unit_movement_type::defense_modifier(const gamemap& map,
|
||||
gamemap::TERRAIN terrain) const
|
||||
{
|
||||
const std::map<gamemap::TERRAIN,double>::const_iterator i =
|
||||
defenseMods_.find(terrain);
|
||||
if(i != defenseMods_.end()) {
|
||||
return i->second;
|
||||
}
|
||||
|
||||
const std::vector<config*>& v = cfg_.children["defense"];
|
||||
if(v.empty())
|
||||
return 1;
|
||||
|
||||
config* defense = v[0];
|
||||
const std::string& name = map.underlying_terrain_name(terrain);
|
||||
const std::string& val = defense->values[name];
|
||||
|
||||
const double res = atof(val.c_str());
|
||||
defenseMods_.insert(std::pair<gamemap::TERRAIN,double>(terrain,res));
|
||||
return res;
|
||||
}
|
||||
|
||||
int unit_movement_type::damage_against(const attack_type& attack) const
|
||||
{
|
||||
const std::vector<config*>& v = cfg_.children["resistance"];
|
||||
if(v.empty())
|
||||
return 1;
|
||||
|
||||
config* resistance = v[0];
|
||||
const std::string& val = resistance->values[attack.type()];
|
||||
const double resist = atof(val.c_str());
|
||||
return static_cast<int>(resist * static_cast<double>(attack.damage()));
|
||||
}
|
||||
|
||||
const std::map<std::string,std::string>& unit_movement_type::damage_table() const
|
||||
{
|
||||
const std::vector<config*>& v = cfg_.children["resistance"];
|
||||
if(v.empty()) {
|
||||
static const std::map<std::string,std::string> default_val;
|
||||
return default_val;
|
||||
}
|
||||
|
||||
return v[0]->values;
|
||||
}
|
||||
|
||||
unit_type::unit_type(config& cfg, const movement_type_map& mv_types,
|
||||
std::vector<config*>& traits)
|
||||
: cfg_(cfg), possibleTraits_(traits), alpha_(1.0)
|
||||
{
|
||||
if(has_ability("heals")) {
|
||||
heals_ = game_config::healer_heals_per_turn;
|
||||
max_heals_ = game_config::heal_amount;
|
||||
} else if(has_ability("cures")) {
|
||||
heals_ = game_config::curer_heals_per_turn;
|
||||
max_heals_ = game_config::cure_amount;
|
||||
} else {
|
||||
heals_ = 0;
|
||||
max_heals_ = 0;
|
||||
}
|
||||
|
||||
heals_ = has_ability("heals");
|
||||
regenerates_ = has_ability("regenerates");
|
||||
leadership_ = has_ability("leadership");
|
||||
lightbringer_ = has_ability("lightbringer");
|
||||
skirmish_ = has_ability("skirmisher");
|
||||
teleport_ = has_ability("teleport");
|
||||
nightvision_ = has_ability("night vision");
|
||||
|
||||
const std::string& alpha_blend = cfg_.values["alpha"];
|
||||
if(alpha_blend.empty() == false) {
|
||||
alpha_ = atof(alpha_blend.c_str());
|
||||
}
|
||||
|
||||
const std::string& move_type = cfg_.values["movement_type"];
|
||||
if(move_type.empty()) {
|
||||
throw gamestatus::load_game_failed("Movement type not specified for "
|
||||
"unit '" + name() + "'");
|
||||
}
|
||||
|
||||
const movement_type_map::const_iterator it = mv_types.find(move_type);
|
||||
if(it == mv_types.end()) {
|
||||
throw gamestatus::load_game_failed("Undefined movement type '" +
|
||||
move_type + "'");
|
||||
}
|
||||
|
||||
movementType_ = &(it->second);
|
||||
|
||||
//check if the images necessary for units exist
|
||||
#ifdef linux
|
||||
struct stat stat_buf;
|
||||
#ifdef WESNOTH_PATH
|
||||
if(::stat((WESNOTH_PATH + std::string("/images/") +
|
||||
cfg_.values["image"]).c_str(),&stat_buf) >= 0) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if(::stat(("images/" + cfg_.values["image"]).c_str(),&stat_buf) < 0) {
|
||||
std::cerr << "image '" << cfg_.values["image"] << "' does not exist!\n";
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string unit_type::id() const
|
||||
{
|
||||
std::string n = name();
|
||||
n.erase(std::remove(n.begin(),n.end(),' '),n.end());
|
||||
return n;
|
||||
}
|
||||
|
||||
std::string unit_type::language_name() const
|
||||
{
|
||||
const std::string& lang_name = string_table[id()];
|
||||
if(lang_name.empty() == false)
|
||||
return lang_name;
|
||||
else
|
||||
return name();
|
||||
}
|
||||
|
||||
const std::string& unit_type::name() const
|
||||
{
|
||||
return cfg_.values["name"];
|
||||
}
|
||||
|
||||
const std::string& unit_type::image() const
|
||||
{
|
||||
return cfg_.values["image"];
|
||||
}
|
||||
|
||||
const std::string& unit_type::image_defensive() const
|
||||
{
|
||||
const std::string& val = cfg_.values["image_defensive"];
|
||||
if(val.empty())
|
||||
return cfg_.values["image"];
|
||||
else
|
||||
return val;
|
||||
}
|
||||
|
||||
const std::string& unit_type::image_profile() const
|
||||
{
|
||||
const std::string& val = cfg_.values["profile"];
|
||||
if(val.size() == 0)
|
||||
return image();
|
||||
else
|
||||
return val;
|
||||
}
|
||||
|
||||
const std::string& unit_type::unit_description() const
|
||||
{
|
||||
static const std::string default_val("No description available");
|
||||
|
||||
const std::string& lang_desc = string_table[id() + "_description"];
|
||||
if(lang_desc.empty() == false)
|
||||
return lang_desc;
|
||||
|
||||
const std::string& desc = cfg_.values["unit_description"];
|
||||
if(desc.empty())
|
||||
return default_val;
|
||||
else
|
||||
return desc;
|
||||
}
|
||||
|
||||
int unit_type::hitpoints() const
|
||||
{
|
||||
return atoi(cfg_.values["hitpoints"].c_str());
|
||||
}
|
||||
|
||||
std::vector<attack_type> unit_type::attacks() const
|
||||
{
|
||||
std::vector<attack_type> res;
|
||||
const std::vector<config*>& v = cfg_.children["attack"];
|
||||
for(std::vector<config*>::const_iterator i = v.begin(); i != v.end(); ++i)
|
||||
res.push_back(attack_type(**i));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
const unit_movement_type& unit_type::movement_type() const
|
||||
{
|
||||
return *movementType_;
|
||||
}
|
||||
|
||||
int unit_type::cost() const
|
||||
{
|
||||
return atoi(cfg_.values["cost"].c_str());
|
||||
}
|
||||
|
||||
int unit_type::experience_needed() const
|
||||
{
|
||||
return atoi(cfg_.values["experience"].c_str());
|
||||
}
|
||||
|
||||
std::vector<std::string> unit_type::advances_to() const
|
||||
{
|
||||
const std::string& val = cfg_.values["advanceto"];
|
||||
if(val == "null" || val == "")
|
||||
return std::vector<std::string>();
|
||||
else
|
||||
return config::split(val);
|
||||
}
|
||||
|
||||
const std::string& unit_type::usage() const
|
||||
{
|
||||
return cfg_.values["usage"];
|
||||
}
|
||||
|
||||
int unit_type::level() const
|
||||
{
|
||||
return atoi(cfg_.values["level"].c_str());
|
||||
}
|
||||
|
||||
int unit_type::movement() const
|
||||
{
|
||||
return atoi(cfg_.values["movement"].c_str());
|
||||
}
|
||||
|
||||
unit_type::ALIGNMENT unit_type::alignment() const
|
||||
{
|
||||
const std::string& align = cfg_.values["alignment"];
|
||||
if(align == "lawful")
|
||||
return LAWFUL;
|
||||
else if(align == "chaotic")
|
||||
return CHAOTIC;
|
||||
else
|
||||
return NEUTRAL;
|
||||
}
|
||||
|
||||
const std::string& unit_type::alignment_description(unit_type::ALIGNMENT align)
|
||||
{
|
||||
static const std::string aligns[] = { "lawful", "neutral", "chaotic" };
|
||||
const std::map<std::string,std::string>::const_iterator i =
|
||||
string_table.find(aligns[align]);
|
||||
if(i != string_table.end())
|
||||
return i->second;
|
||||
else
|
||||
return aligns[align];
|
||||
}
|
||||
|
||||
double unit_type::alpha() const
|
||||
{
|
||||
return alpha_;
|
||||
}
|
||||
|
||||
const std::string& unit_type::ability() const
|
||||
{
|
||||
return cfg_.values["ability"];
|
||||
}
|
||||
|
||||
int unit_type::max_unit_healing() const
|
||||
{
|
||||
return max_heals_;
|
||||
}
|
||||
|
||||
int unit_type::heals() const
|
||||
{
|
||||
return heals_;
|
||||
}
|
||||
|
||||
bool unit_type::regenerates() const
|
||||
{
|
||||
return regenerates_;
|
||||
}
|
||||
|
||||
bool unit_type::is_leader() const
|
||||
{
|
||||
return leadership_;
|
||||
}
|
||||
|
||||
bool unit_type::is_lightbringer() const
|
||||
{
|
||||
return lightbringer_;
|
||||
}
|
||||
|
||||
bool unit_type::is_skirmisher() const
|
||||
{
|
||||
return skirmish_;
|
||||
}
|
||||
|
||||
bool unit_type::teleports() const
|
||||
{
|
||||
return teleport_;
|
||||
}
|
||||
|
||||
bool unit_type::nightvision() const
|
||||
{
|
||||
return nightvision_;
|
||||
}
|
||||
|
||||
bool unit_type::has_ability(const std::string& ability) const
|
||||
{
|
||||
return config::has_value(this->ability(),ability);
|
||||
}
|
||||
|
||||
const std::vector<config*>& unit_type::possible_traits() const
|
||||
{
|
||||
return possibleTraits_;
|
||||
}
|
||||
|
||||
game_data::game_data(config& cfg)
|
||||
{
|
||||
std::vector<config*>& unit_traits = cfg.children["trait"];
|
||||
|
||||
std::vector<config*>& move_types = cfg.children["movetype"];
|
||||
for(std::vector<config*>::iterator i = move_types.begin();
|
||||
i != move_types.end(); ++i) {
|
||||
const unit_movement_type move_type(**i);
|
||||
movement_types.insert(
|
||||
std::pair<std::string,unit_movement_type>(move_type.name(),
|
||||
move_type));
|
||||
}
|
||||
|
||||
std::vector<config*>& u_types = cfg.children["unit"];
|
||||
for(std::vector<config*>::iterator j = u_types.begin();
|
||||
j != u_types.end(); ++j) {
|
||||
const unit_type u_type(**j,movement_types,unit_traits);
|
||||
unit_types.insert(
|
||||
std::pair<std::string,unit_type>(u_type.name(),u_type));
|
||||
}
|
||||
}
|
192
src/unit_types.hpp
Normal file
192
src/unit_types.hpp
Normal file
|
@ -0,0 +1,192 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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 UNIT_TYPES_H_INCLUDED
|
||||
#define UNIT_TYPES_H_INCLUDED
|
||||
|
||||
#include "config.hpp"
|
||||
#include "map.hpp"
|
||||
#include "team.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
//the 'attack type' is the type of attack, how many times it strikes,
|
||||
//and how much damage it does.
|
||||
class attack_type
|
||||
{
|
||||
public:
|
||||
enum RANGE { SHORT_RANGE, LONG_RANGE };
|
||||
|
||||
attack_type(config& cfg);
|
||||
const std::string& name() const;
|
||||
const std::string& type() const;
|
||||
const std::string& special() const;
|
||||
RANGE range() const;
|
||||
int damage() const;
|
||||
int num_attacks() const;
|
||||
|
||||
enum FRAME_TYPE { UNIT_FRAME, MISSILE_FRAME };
|
||||
enum FRAME_DIRECTION { VERTICAL, DIAGONAL };
|
||||
|
||||
int get_first_frame(FRAME_TYPE type=UNIT_FRAME) const;
|
||||
int get_last_frame(FRAME_TYPE type=UNIT_FRAME) const;
|
||||
|
||||
//function which gets an attack animation frame. The argument
|
||||
//is 0 for the frame at the time of impact, and negative for
|
||||
//frames before the time of impact
|
||||
const std::string* get_frame(int milliseconds, FRAME_TYPE type=UNIT_FRAME,
|
||||
FRAME_DIRECTION direction=VERTICAL) const;
|
||||
|
||||
struct sfx {
|
||||
int time;
|
||||
std::string on_hit, on_miss;
|
||||
};
|
||||
|
||||
const std::vector<sfx>& sound_effects() const;
|
||||
|
||||
bool matches_filter(config& cfg) const;
|
||||
void apply_modification(config& cfg);
|
||||
private:
|
||||
std::string name_;
|
||||
std::string type_;
|
||||
std::string special_;
|
||||
RANGE range_;
|
||||
int damage_;
|
||||
int num_attacks_;
|
||||
|
||||
struct frame {
|
||||
frame(int i1, int i2, const std::string& img)
|
||||
: start(i1), end(i2), image(&img), image_diagonal(NULL)
|
||||
{}
|
||||
|
||||
frame(int i1, int i2, const std::string& img, const std::string& diag)
|
||||
: start(i1), end(i2), image(&img), image_diagonal(&diag)
|
||||
{}
|
||||
|
||||
int start, end;
|
||||
const std::string* image;
|
||||
const std::string* image_diagonal;
|
||||
};
|
||||
|
||||
std::vector<frame> frames_[2];
|
||||
|
||||
std::vector<sfx> sfx_;
|
||||
};
|
||||
|
||||
//the 'unit movement type' is the basic size of the unit - flying, small land,
|
||||
//large land, etc etc.
|
||||
class unit_movement_type
|
||||
{
|
||||
public:
|
||||
//this class assumes that the passed in reference will remain valid
|
||||
//for at least as long as the class instance
|
||||
unit_movement_type(config& cfg);
|
||||
|
||||
const std::string& name() const;
|
||||
int movement_cost(const gamemap& map, gamemap::TERRAIN terrain) const;
|
||||
double defense_modifier(const gamemap& map, gamemap::TERRAIN terrain) const;
|
||||
int damage_against(const attack_type& attack) const;
|
||||
|
||||
const std::map<std::string,std::string>& damage_table() const;
|
||||
|
||||
private:
|
||||
mutable config& cfg_;
|
||||
|
||||
mutable std::map<gamemap::TERRAIN,int> moveCosts_;
|
||||
mutable std::map<gamemap::TERRAIN,double> defenseMods_;
|
||||
};
|
||||
|
||||
typedef std::map<std::string,unit_movement_type> movement_type_map;
|
||||
|
||||
class unit_type
|
||||
{
|
||||
public:
|
||||
//this class assumes that the passed in references will remain valid
|
||||
//for at least as long as the class instance
|
||||
unit_type(config& cfg, const movement_type_map& movement_types,
|
||||
std::vector<config*>& traits);
|
||||
|
||||
//the name of the unit in the current language setting
|
||||
std::string language_name() const;
|
||||
|
||||
//unique identifier that doesn't have any whitespace
|
||||
std::string id() const;
|
||||
const std::string& name() const;
|
||||
const std::string& image() const;
|
||||
const std::string& image_profile() const;
|
||||
const std::string& image_defensive() const;
|
||||
const std::string& unit_description() const;
|
||||
int hitpoints() const;
|
||||
std::vector<attack_type> attacks() const;
|
||||
const unit_movement_type& movement_type() const;
|
||||
|
||||
int experience_needed() const;
|
||||
std::vector<std::string> advances_to() const;
|
||||
const std::string& usage() const;
|
||||
|
||||
int level() const;
|
||||
int movement() const;
|
||||
int cost() const;
|
||||
|
||||
enum ALIGNMENT { LAWFUL, NEUTRAL, CHAOTIC };
|
||||
|
||||
ALIGNMENT alignment() const;
|
||||
static const std::string& alignment_description(ALIGNMENT align);
|
||||
|
||||
double alpha() const;
|
||||
|
||||
const std::string& ability() const;
|
||||
|
||||
//max_unit_healing returns the maximum hitpoints a unit next to this
|
||||
//unit can heal per turn. heals returns the total amount of hitpoints
|
||||
//this unit can heal out of all adjacent units
|
||||
int max_unit_healing() const;
|
||||
int heals() const;
|
||||
bool regenerates() const;
|
||||
bool is_leader() const;
|
||||
bool is_lightbringer() const;
|
||||
bool is_skirmisher() const;
|
||||
bool teleports() const;
|
||||
bool nightvision() const;
|
||||
|
||||
bool has_ability(const std::string& ability) const;
|
||||
|
||||
const std::vector<config*>& possible_traits() const;
|
||||
|
||||
private:
|
||||
mutable config& cfg_;
|
||||
|
||||
double alpha_;
|
||||
|
||||
int max_heals_;
|
||||
int heals_;
|
||||
bool regenerates_;
|
||||
bool leadership_;
|
||||
bool lightbringer_;
|
||||
bool skirmish_;
|
||||
bool teleport_;
|
||||
bool nightvision_;
|
||||
|
||||
const unit_movement_type* movementType_;
|
||||
|
||||
std::vector<config*>& possibleTraits_;
|
||||
};
|
||||
|
||||
struct game_data
|
||||
{
|
||||
game_data(config& cfg);
|
||||
movement_type_map movement_types;
|
||||
std::map<std::string,unit_type> unit_types;
|
||||
};
|
||||
|
||||
#endif
|
32
src/util.hpp
Normal file
32
src/util.hpp
Normal file
|
@ -0,0 +1,32 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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 UTIL_H_INCLUDED
|
||||
#define UTIL_H_INCLUDED
|
||||
|
||||
#include <map>
|
||||
|
||||
//instead of playing with VC++'s crazy definitions of min and max,
|
||||
//just define our own
|
||||
template<typename T>
|
||||
T& minimum(T& a, T& b) { return a < b ? a : b; }
|
||||
|
||||
template<typename T>
|
||||
const T& minimum(const T& a, const T& b) { return a < b ? a : b; }
|
||||
|
||||
template<typename T>
|
||||
T& maximum(T& a, T& b) { return a < b ? b : a; }
|
||||
|
||||
template<typename T>
|
||||
const T& maximum(const T& a, const T& b) { return a < b ? b : a; }
|
||||
|
||||
#endif
|
328
src/video.cpp
Normal file
328
src/video.cpp
Normal file
|
@ -0,0 +1,328 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <iostream>
|
||||
|
||||
#include "video.hpp"
|
||||
|
||||
#define TEST_VIDEO_ON 0
|
||||
|
||||
#if (TEST_VIDEO_ON==1)
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
//test program takes three args - x-res y-res colour-depth
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
if( argc != 4 ) {
|
||||
printf( "usage: %s x-res y-res bitperpixel\n", argv[0] );
|
||||
return 1;
|
||||
}
|
||||
SDL_Init(SDL_INIT_VIDEO);
|
||||
CVideo video;
|
||||
|
||||
printf( "args: %s, %s, %s\n", argv[1], argv[2], argv[3] );
|
||||
|
||||
printf( "(%d,%d,%d)\n", strtoul(argv[1],0,10), strtoul(argv[2],0,10),
|
||||
strtoul(argv[3],0,10) );
|
||||
|
||||
if( video.setMode( strtoul(argv[1],0,10), strtoul(argv[2],0,10),
|
||||
strtoul(argv[3],0,10), FULL_SCREEN ) ) {
|
||||
printf( "video mode possible\n" );
|
||||
} else printf( "video mode NOT possible\n" );
|
||||
printf( "%d, %d, %d\n", video.getx(), video.gety(),
|
||||
video.getBitsPerPixel() );
|
||||
|
||||
for( int s = 0; s < 50; s++ ) {
|
||||
video.lock();
|
||||
for( int i = 0; i < video.getx(); i++ )
|
||||
video.setPixel( i, 90, 255, 0, 0 );
|
||||
if( s%10==0)
|
||||
printf( "frame %d\n", s );
|
||||
video.unlock();
|
||||
video.update( 0, 90, video.getx(), 1 );
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
bool fullScreen = false;
|
||||
}
|
||||
|
||||
CVideo::CVideo(const char* text) : frameBuffer(NULL), backBuffer(NULL)
|
||||
{
|
||||
const int res =
|
||||
SDL_Init( SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_NOPARACHUTE);
|
||||
|
||||
if(res < 0) {
|
||||
std::cerr << "Could not initialize SDL: " << SDL_GetError() << "\n";
|
||||
throw CVideo::error();
|
||||
}
|
||||
|
||||
for(int i = 0; i != sizeof(text_); ++i) {
|
||||
text_[i] = text[i];
|
||||
}
|
||||
}
|
||||
|
||||
CVideo::CVideo( int x, int y, int bits_per_pixel, int flags, const char* text )
|
||||
: frameBuffer(NULL), backBuffer(NULL)
|
||||
{
|
||||
const int res = SDL_Init( SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_NOPARACHUTE);
|
||||
if(res < 0) {
|
||||
throw error();
|
||||
}
|
||||
|
||||
setMode( x, y, bits_per_pixel, flags );
|
||||
|
||||
for(int i = 0; i != sizeof(text_); ++i) {
|
||||
text_[i] = text[i];
|
||||
}
|
||||
}
|
||||
|
||||
CVideo::~CVideo()
|
||||
{
|
||||
SDL_FreeSurface( backBuffer );
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
int CVideo::modePossible( int x, int y, int bits_per_pixel, int flags )
|
||||
{
|
||||
return SDL_VideoModeOK( x, y, bits_per_pixel, flags );
|
||||
}
|
||||
|
||||
int CVideo::setMode( int x, int y, int bits_per_pixel, int flags )
|
||||
{
|
||||
const int res = SDL_VideoModeOK( x, y, bits_per_pixel, flags );
|
||||
|
||||
if( res == 0 )
|
||||
return 0;
|
||||
|
||||
fullScreen = (flags & FULL_SCREEN) != 0;
|
||||
frameBuffer = SDL_SetVideoMode( x, y, bits_per_pixel, flags );
|
||||
|
||||
if( frameBuffer != NULL ) {
|
||||
if(backBuffer != NULL)
|
||||
SDL_FreeSurface(backBuffer);
|
||||
|
||||
backBuffer = SDL_ConvertSurface( frameBuffer,
|
||||
frameBuffer->format, 0 );
|
||||
if( backBuffer == NULL )
|
||||
fprintf( stderr, "out of memory\n" );
|
||||
return bits_per_pixel;
|
||||
} else return 0;
|
||||
}
|
||||
|
||||
int CVideo::getx() const
|
||||
{
|
||||
return backBuffer->w;
|
||||
}
|
||||
|
||||
int CVideo::gety() const
|
||||
{
|
||||
return backBuffer->h;
|
||||
}
|
||||
|
||||
int CVideo::getBitsPerPixel()
|
||||
{
|
||||
return backBuffer->format->BitsPerPixel;
|
||||
}
|
||||
|
||||
int CVideo::getBytesPerPixel()
|
||||
{
|
||||
return backBuffer->format->BytesPerPixel;
|
||||
}
|
||||
|
||||
int CVideo::getRedMask()
|
||||
{
|
||||
return backBuffer->format->Rmask;
|
||||
}
|
||||
|
||||
int CVideo::getGreenMask()
|
||||
{
|
||||
return backBuffer->format->Gmask;
|
||||
}
|
||||
|
||||
int CVideo::getBlueMask()
|
||||
{
|
||||
return backBuffer->format->Bmask;
|
||||
}
|
||||
|
||||
void* CVideo::getAddress()
|
||||
{
|
||||
return backBuffer->pixels;
|
||||
}
|
||||
|
||||
void CVideo::lock()
|
||||
{
|
||||
if( SDL_MUSTLOCK(backBuffer) )
|
||||
SDL_LockSurface( backBuffer );
|
||||
}
|
||||
|
||||
void CVideo::unlock()
|
||||
{
|
||||
if( SDL_MUSTLOCK(backBuffer) )
|
||||
SDL_UnlockSurface( backBuffer );
|
||||
}
|
||||
|
||||
int CVideo::mustLock()
|
||||
{
|
||||
return SDL_MUSTLOCK(backBuffer);
|
||||
}
|
||||
|
||||
void CVideo::setPixel( int x, int y, int r, int g, int b )
|
||||
{
|
||||
int pixel = ((r<<(backBuffer->format->Rshift-backBuffer->format->Rloss))
|
||||
& backBuffer->format->Rmask)+
|
||||
((g<<(backBuffer->format->Gshift-backBuffer->format->Gloss))
|
||||
& backBuffer->format->Gmask)+
|
||||
((b>>(backBuffer->format->Bloss-backBuffer->format->Bshift))
|
||||
& backBuffer->format->Bmask);
|
||||
|
||||
setPixel( x, y, pixel );
|
||||
}
|
||||
|
||||
int CVideo::convertColour(int r, int g, int b)
|
||||
{
|
||||
return ((r<<(backBuffer->format->Rshift-backBuffer->format->Rloss))
|
||||
& backBuffer->format->Rmask)+
|
||||
((g<<(backBuffer->format->Gshift-backBuffer->format->Gloss))
|
||||
& backBuffer->format->Gmask)+
|
||||
((b>>(backBuffer->format->Bloss-backBuffer->format->Bshift))
|
||||
& backBuffer->format->Bmask);
|
||||
}
|
||||
|
||||
void CVideo::setPixel( int x, int y, int p )
|
||||
{
|
||||
static int pixel;
|
||||
static char* p1 = ((char*)&pixel);
|
||||
static char* p2 = ((char*)&pixel)+1;
|
||||
static char* p3 = ((char*)&pixel)+2;
|
||||
static short* sp = ((short*)&pixel);
|
||||
pixel = p;
|
||||
|
||||
if( x < 0 || x >= backBuffer->w || y < 0 || y >= backBuffer->h )
|
||||
return;
|
||||
|
||||
switch( backBuffer->format->BytesPerPixel ) {
|
||||
case 1:
|
||||
*((char*)backBuffer->pixels+y*backBuffer->w+x) = *p1;
|
||||
break;
|
||||
case 2:
|
||||
*((short*)backBuffer->pixels+y*backBuffer->w+x) = *sp;
|
||||
break;
|
||||
case 3:
|
||||
*((char*)backBuffer->pixels+y*backBuffer->w*3+x*3)
|
||||
= *p1;
|
||||
*((char*)backBuffer->pixels+y*backBuffer->w*3+x*3+1)
|
||||
= *p2;
|
||||
*((char*)backBuffer->pixels+y*backBuffer->w*3+x*3+2)
|
||||
= *p3;
|
||||
break;
|
||||
case 4:
|
||||
*((int*)backBuffer->pixels+y*backBuffer->w+x) = pixel;
|
||||
break;
|
||||
default:
|
||||
fprintf( stderr, "Unknown colour depth\n" );
|
||||
}
|
||||
}
|
||||
|
||||
void CVideo::update( int x, int y, int w, int h )
|
||||
{
|
||||
if( w == 0 || h == 0 )
|
||||
return;
|
||||
|
||||
if( x < 0 ) {
|
||||
w += x;
|
||||
x = 0;
|
||||
}
|
||||
|
||||
if( y < 0 ) {
|
||||
h += y;
|
||||
y = 0;
|
||||
}
|
||||
|
||||
if( x+w > frameBuffer->w )
|
||||
w = frameBuffer->w - x;
|
||||
|
||||
if( y+h > frameBuffer->h )
|
||||
h = frameBuffer->h - y;
|
||||
|
||||
SRectangle rect = {x,y,w,h};
|
||||
SDL_BlitSurface( backBuffer, &rect, frameBuffer, &rect );
|
||||
SDL_UpdateRect( frameBuffer, x, y, w, h );
|
||||
}
|
||||
|
||||
void CVideo::update( SRectangle* rect )
|
||||
{
|
||||
SDL_BlitSurface( backBuffer, rect, frameBuffer, rect );
|
||||
SDL_UpdateRect( frameBuffer, rect->x, rect->y, rect->w, rect->h );
|
||||
}
|
||||
|
||||
SDL_Surface* CVideo::getSurface( void )
|
||||
{
|
||||
return backBuffer;
|
||||
}
|
||||
|
||||
void CVideo::drawChar(int x, int y, int pixel, int bg, char c, int sz)
|
||||
{
|
||||
const char* const data = text_ + c*8;
|
||||
for(int i = 0; i != 8*sz; ++i) {
|
||||
if(y+i >= backBuffer->h)
|
||||
return;
|
||||
|
||||
for(int j = 0; j != 8*sz; ++j) {
|
||||
if(x+j >= backBuffer->w)
|
||||
break;
|
||||
|
||||
if(data[i/sz] & (128 >> (j/sz))) {
|
||||
setPixel(x+j,y+i,pixel);
|
||||
} else {
|
||||
if(bg != pixel)
|
||||
setPixel(x+j,y+i,bg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int CVideo::drawText(int x, int y, int pixel, int bg, const char* text, int sz)
|
||||
{
|
||||
const int good_colour = 0x0F00;
|
||||
const int bad_colour = 0xF000;
|
||||
int colour = pixel;
|
||||
const int startx = x;
|
||||
const int starty = y;
|
||||
while(*text) {
|
||||
if(*text == '@') {
|
||||
colour = good_colour;
|
||||
} else if(*text == '#') {
|
||||
colour = bad_colour;
|
||||
} else if(*text == '\n') {
|
||||
colour = pixel;
|
||||
y += 8*sz;
|
||||
x = startx;
|
||||
} else {
|
||||
drawChar(x,y,colour,bg == pixel ? colour : bg,*text,sz);
|
||||
x += 8*sz;
|
||||
}
|
||||
++text;
|
||||
}
|
||||
|
||||
y += 8*sz;
|
||||
|
||||
return y - starty;
|
||||
}
|
||||
|
||||
bool CVideo::isFullScreen() const { return fullScreen; }
|
71
src/video.hpp
Normal file
71
src/video.hpp
Normal file
|
@ -0,0 +1,71 @@
|
|||
/* $Id$ */
|
||||
/*
|
||||
Copyright (C) 2003 by David White <davidnwhite@optusnet.com.au>
|
||||
Part of the Battle for Wesnoth Project http://wesnoth.whitevine.net
|
||||
|
||||
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 VIDEO_HPP_INCLUDED
|
||||
#define VIDEO_HPP_INCLUDED
|
||||
|
||||
#include "SDL.h"
|
||||
|
||||
//possible flags when setting video modes
|
||||
#define FULL_SCREEN SDL_FULLSCREEN
|
||||
#define VIDEO_MEMORY SDL_HWSURFACE
|
||||
#define SYSTEM_MEMORY SDL_SWSURFACE
|
||||
|
||||
typedef SDL_Rect SRectangle;
|
||||
|
||||
class CVideo {
|
||||
public:
|
||||
CVideo(const char* text);
|
||||
CVideo( int x, int y, int bits_per_pixel, int flags, const char* text );
|
||||
~CVideo();
|
||||
|
||||
int modePossible( int x, int y, int bits_per_pixel, int flags );
|
||||
int setMode( int x, int y, int bits_per_pixel, int flags );
|
||||
|
||||
//functions to get the dimensions of the current video-mode
|
||||
int getx() const;
|
||||
int gety() const;
|
||||
int getBitsPerPixel();
|
||||
int getBytesPerPixel();
|
||||
int getRedMask();
|
||||
int getGreenMask();
|
||||
int getBlueMask();
|
||||
|
||||
//functions to access the screen
|
||||
void* getAddress();
|
||||
void lock();
|
||||
void unlock();
|
||||
int mustLock();
|
||||
void setPixel( int x, int y, int r, int g, int b );
|
||||
void setPixel( int x, int y, int pixel );
|
||||
int convertColour(int r, int g, int b);
|
||||
void update( int x, int y, int w, int h );
|
||||
void update( SRectangle* area );
|
||||
|
||||
SDL_Surface* getSurface( void );
|
||||
|
||||
int drawText(int x, int y, int pixel, int bg, const char* text,int size=1);
|
||||
|
||||
bool isFullScreen() const;
|
||||
|
||||
struct error {};
|
||||
|
||||
private:
|
||||
|
||||
void drawChar(int x, int y, int pixel, int bg, char c, int size=1);
|
||||
|
||||
SDL_Surface* frameBuffer;
|
||||
SDL_Surface* backBuffer;
|
||||
char text_[256*8];
|
||||
};
|
||||
|
||||
#endif
|
Loading…
Add table
Reference in a new issue