widelands-dev team mailing list archive
-
widelands-dev team
-
Mailing list archive
-
Message #03449
[Merge] lp:~widelands-dev/widelands/find_attack_soldiers into lp:widelands
SirVer has proposed merging lp:~widelands-dev/widelands/find_attack_soldiers into lp:widelands.
Requested reviews:
Widelands Developers (widelands-dev)
For more details, see:
https://code.launchpad.net/~widelands-dev/widelands/find_attack_soldiers/+merge/245276
This patch was send to me by fk who has difficulties accessing launchpad.
I did some changes to make it compile, but I did not review nor test the changes. So I do not fully know what the code actually does, but it has something to do with better selection of soldiers on attack.
--
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/find_attack_soldiers into lp:widelands.
=== modified file 'src/ai/ai_help_structs.h'
--- src/ai/ai_help_structs.h 2014-10-30 20:24:57 +0000
+++ src/ai/ai_help_structs.h 2014-12-20 20:51:12 +0000
@@ -21,6 +21,7 @@
#define WL_AI_AI_HELP_STRUCTS_H
#include <list>
+#include <unordered_set>
#include "economy/flag.h"
#include "economy/road.h"
@@ -32,6 +33,7 @@
#include "logic/player.h"
#include "logic/world/terrain_description.h"
#include "logic/world/world.h"
+#include "logic/ship.h"
namespace Widelands {
@@ -187,6 +189,14 @@
Widelands::FCoords coords;
int32_t blocked_until_;
+ bool operator== (const BlockedField& other) const {
+ return coords == other.coords;
+ }
+
+ bool operator!= (const BlockedField& other) const {
+ return coords != other.coords;
+ }
+
BlockedField(Widelands::FCoords c, int32_t until) : coords(c), blocked_until_(until) {
}
};
@@ -227,10 +237,9 @@
int16_t military_stationed_;
// stationed (manned) military buildings nearby
int16_t military_unstationed_;
- // some buildings must be postponed bit
- int32_t prohibited_till_;
- // and then some must be forced
- int32_t forced_after_;
+ bool is_portspace_;
+ bool port_nearby_; // to increase priority if a port is nearby,
+ // especially for new colonies
std::vector<uint8_t> consumers_nearby_;
std::vector<uint8_t> producers_nearby_;
@@ -261,7 +270,9 @@
military_loneliness_(1000),
military_in_constr_nearby_(0),
military_presence_(0),
- military_stationed_(0) {
+ military_stationed_(0),
+ is_portspace_(false),
+ port_nearby_(false) {
}
};
@@ -310,12 +321,14 @@
bool plants_trees_;
bool recruitment_; // is "producing" workers?
bool is_buildable_;
- bool need_trees_; // lumberjack = true
- bool need_stones_; // quarry = true
- bool mines_water_; // wells
- bool need_water_; // fisher, fish_breeder = true
- bool is_hunter_; // need to identify hunters
- bool is_fisher_; // need to identify fishers
+ bool need_trees_; // lumberjack = true
+ bool need_stones_; // quarry = true
+ bool mines_water_; // wells
+ bool need_water_; // fisher, fish_breeder = true
+ bool is_hunter_; // need to identify hunters
+ bool is_fisher_; // need to identify fishers
+ bool is_port_;
+ bool is_shipyard_;
bool space_consumer_; // farm, vineyard... = true
bool expansion_type_; // military building used that can be used to control area
bool fighting_type_; // military building built near enemies
@@ -327,7 +340,6 @@
int32_t mines_; // type of resource it mines_
uint16_t mines_percent_; // % of res it can mine
-
uint32_t current_stats_;
std::vector<int16_t> inputs_;
@@ -372,6 +384,34 @@
bool enemies_nearby_;
};
+struct TrainingSiteObserver {
+ Widelands::TrainingSite* site;
+ BuildingObserver* bo;
+};
+
+struct WarehouseSiteObserver {
+ Widelands::Warehouse* site;
+ BuildingObserver* bo;
+};
+
+struct ShipObserver {
+
+ bool operator== (const ShipObserver& other) const {
+ return ship->serial() == other.ship->serial();
+ }
+
+ bool operator!= (const ShipObserver& other) const {
+ return ship->serial() != other.ship->serial();
+ }
+
+ Widelands::Ship* ship;
+ Widelands::Coords expedition_start_point_;
+ std::unordered_set<uint32_t> visited_spots_;
+ bool island_circ_direction = true; // a ship circumvents all island in the same direction
+ bool waiting_for_command_ = false;
+ int32_t last_command_time = 0;
+};
+
struct WareObserver {
uint8_t producers_;
uint8_t consumers_;
=== modified file 'src/ai/defaultai.cc'
--- src/ai/defaultai.cc 2014-11-30 18:53:22 +0000
+++ src/ai/defaultai.cc 2014-12-20 20:51:12 +0000
@@ -31,7 +31,9 @@
#include "base/macros.h"
#include "economy/economy.h"
#include "economy/flag.h"
+#include "economy/portdock.h"
#include "economy/road.h"
+#include "economy/wares_queue.h"
#include "logic/constructionsite.h"
#include "logic/findbob.h"
#include "logic/findimmovable.h"
@@ -39,7 +41,9 @@
#include "logic/map.h"
#include "logic/militarysite.h"
#include "logic/player.h"
+#include "logic/playercommand.h"
#include "logic/productionsite.h"
+#include "logic/ship.h"
#include "logic/trainingsite.h"
#include "logic/tribe.h"
#include "logic/warehouse.h"
@@ -48,6 +52,7 @@
// Building of new military buildings can be restricted
constexpr int kPushExpansion = 1;
+
constexpr int kResourcesOrDefense = 2;
constexpr int kDefenseOnly = 3;
constexpr int kNoNewMilitary = 4;
@@ -59,6 +64,10 @@
// building of the same building can be started after 25s at earliest
constexpr int kBuildingMinInterval = 25 * 1000;
constexpr int kMinBFCheckInterval = 5 * 1000;
+constexpr int kShipCheckInterval = 5 * 1000;
+constexpr int kMarineDecisionInterval = 20 * 1000;
+constexpr int kTrainingSitesCheckInterval = 30 * 1000;
+
// Some buildings have to be built close to borders and their
// priority might be decreased below 0, so this is to
// compensate
@@ -81,6 +90,7 @@
num_constructionsites_(0),
num_milit_constructionsites(0),
num_prod_constructionsites(0),
+ num_ports(0),
next_road_due_(2000),
next_stats_update_due_(30000),
next_construction_due_(1000),
@@ -88,9 +98,12 @@
next_productionsite_check_due_(0),
next_mine_check_due_(0),
next_militarysite_check_due_(0),
+ next_ship_check_due(5 * 60 * 1000),
+ next_marine_decisions_due(5 * 60 * 1000),
next_attack_consideration_due_(300000),
- next_helpersites_check_due_(180000),
+ next_trainingsites_check_due_(15 * 60 * 1000),
next_bf_check_due_(1000),
+ next_wares_review_due_(15 * 60 * 1000),
inhibit_road_building_(0),
time_of_last_construction_(0),
enemy_last_seen_(-2 * 60 * 1000),
@@ -107,11 +120,13 @@
last_attack_target_(
std::numeric_limits<uint16_t>::max(), std::numeric_limits<uint16_t>::max()),
next_attack_waittime_(10),
+ seafaring_economy(false),
+ colony_scan_area_(35),
spots_(0) {
// Subscribe to NoteFieldPossession.
field_possession_subscriber_ =
- Notifications::subscribe<NoteFieldPossession>([this](const NoteFieldPossession& note) {
+ Notifications::subscribe<NoteFieldPossession>([this](const NoteFieldPossession& note) {
if (note.player != player_) {
return;
}
@@ -122,7 +137,7 @@
// Subscribe to NoteImmovables.
immovable_subscriber_ =
- Notifications::subscribe<NoteImmovable>([this](const NoteImmovable& note) {
+ Notifications::subscribe<NoteImmovable>([this](const NoteImmovable& note) {
if (note.pi->owner().player_number() != player_->player_number()) {
return;
}
@@ -143,6 +158,49 @@
out_of_resources_site(*note.ps);
});
+
+ // Subscribe to ShipNotes.
+ shipnotes_subscriber_ =
+ Notifications::subscribe<NoteShipMessage>([this](const NoteShipMessage& note) {
+ if (note.ship->get_owner()->player_number() != player_->player_number()) {
+ return;
+ }
+
+ switch (note.message) {
+
+ case NoteShipMessage::Message::kGained:
+
+ marineTaskQueue_.push_back(kStopShipyard);
+
+ allships.push_back(ShipObserver());
+ allships.back().ship = note.ship;
+ if (game().get_gametime() % 2 == 0) {
+ allships.back().island_circ_direction = true;
+ } else {
+ allships.back().island_circ_direction = false;
+ }
+ break;
+
+ case NoteShipMessage::Message::kLost: {
+ for (const ShipObserver& ship_observer : allships) {
+ if (ship_observer.ship == note.ship) {
+ allships.remove(ship_observer);
+ break;
+ }
+ }
+ case NoteShipMessage::Message::kWaitingForCommand:
+ for (ShipObserver& ship_observer : allships) {
+ if (ship_observer.ship == note.ship) {
+ ship_observer.waiting_for_command_ = true;
+ break;
+ }
+ }
+ break;
+ default:
+ ;
+ }
+ }
+ });
}
DefaultAI::~DefaultAI() {
@@ -238,6 +296,14 @@
return;
}
+ if (check_ships(gametime)) {
+ return;
+ }
+
+ if (marine_main_decisions(gametime)) {
+ return;
+ }
+
// Check the mines and consider upgrading or destroying one
if (check_mines_(gametime)) {
return;
@@ -249,6 +315,10 @@
return;
}
+ if (check_trainingsites(gametime)) {
+ return;
+ }
+
// improve existing roads!
// main part of this improvment is creation 'shortcut roads'
// this includes also connection of new buildings
@@ -257,6 +327,12 @@
m_mineable_changed = true;
return;
}
+
+ // once in 15 minutes we increase(or decrease) targets for wares
+ if (next_wares_review_due_ <= gametime) {
+ next_wares_review_due_ = gametime + 15 * 60 * 1000;
+ review_wares_targets(gametime);
+ }
}
/**
@@ -317,6 +393,7 @@
bo.mountain_conqueror_ = bh.is_mountain_conqueror();
bo.prohibited_till_ = bh.get_prohibited_till() * 1000; // value in conf is in seconds
bo.forced_after_ = bh.get_forced_after() * 1000; // value in conf is in seconds
+ bo.is_port_ = bld.get_isport();
if (char const* const s = bh.get_renews_map_resource()) {
bo.production_hint_ = tribe_->safe_ware_index(s);
}
@@ -330,8 +407,7 @@
// Read all interesting data from ware producing buildings
if (bld.type() == MapObjectType::PRODUCTIONSITE) {
- const ProductionSiteDescr& prod =
- dynamic_cast<const ProductionSiteDescr&>(bld);
+ const ProductionSiteDescr& prod = dynamic_cast<const ProductionSiteDescr&>(bld);
bo.type = bld.get_ismine() ? BuildingObserver::MINE : BuildingObserver::PRODUCTIONSITE;
for (const WareAmount& temp_input : prod.inputs()) {
bo.inputs_.push_back(temp_input.first);
@@ -362,6 +438,13 @@
} else {
bo.is_fisher_ = false;
}
+
+ if (building_name == "shipyard") {
+ bo.is_shipyard_ = true;
+ } else {
+ bo.is_shipyard_ = false;
+ }
+
continue;
}
@@ -370,8 +453,7 @@
// non critical are excluded (see below)
if (bld.type() == MapObjectType::MILITARYSITE) {
bo.type = BuildingObserver::MILITARYSITE;
- const MilitarySiteDescr& milit =
- dynamic_cast<const MilitarySiteDescr&>(bld);
+ const MilitarySiteDescr& milit = dynamic_cast<const MilitarySiteDescr&>(bld);
for (const std::pair<unsigned char, unsigned char>& temp_buildcosts : milit.buildcost()) {
// bellow are non-critical wares
if (tribe_->ware_index("log") == temp_buildcosts.first ||
@@ -394,6 +476,10 @@
if (bld.type() == MapObjectType::TRAININGSITE) {
bo.type = BuildingObserver::TRAININGSITE;
+ const TrainingSiteDescr& train = dynamic_cast<const TrainingSiteDescr&>(bld);
+ for (const WareAmount& temp_input : train.inputs()) {
+ bo.inputs_.push_back(temp_input.first);
+ }
continue;
}
@@ -593,6 +679,34 @@
}
}
+ // if curent port is a portspace we need add near fields to a list
+ // of fields prohibited for buildings
+ if (!field.is_portspace_) { // if we know it, no need to do it once more
+ if (player_->get_buildcaps(field.coords) & BUILDCAPS_PORT) {
+ field.is_portspace_ = true;
+ seafaring_economy = true;
+ // blocking fields in vicinity
+ MapRegion<Area<FCoords>> mr(map, Area<FCoords>(map.get_fcoords(field.coords), 3));
+ do {
+ const int32_t hash = coords_hash(map.get_fcoords(*(mr.location().field)));
+ if (port_reserved_coords.count(hash) == 0)
+ port_reserved_coords.insert(hash);
+ } while (mr.advance(map));
+ }
+ }
+
+ // testing if a port is nearby, such field will get a priority boost
+ uint16_t nearest_distance = std::numeric_limits<uint16_t>::max();
+ for (const WarehouseSiteObserver& wh_obs : warehousesites) {
+ const uint16_t actual_distance = map.calc_distance(field.coords, wh_obs.site->get_position());
+ nearest_distance = std::min(nearest_distance, actual_distance);
+ }
+ if (nearest_distance < 15) {
+ field.port_nearby_ = true;
+ } else {
+ field.port_nearby_ = false;
+ }
+
// collect information about resources in the area
std::vector<ImmovableFound> immovables;
// Search in a radius of range
@@ -704,7 +818,6 @@
field.stones_nearby_ = 0;
for (uint32_t j = 0; j < immovables.size(); ++j) {
- // const BaseImmovable & base_immovable = *immovables.at(i).object;
if (immovables.at(j).object->has_attribute(stone_attr)) {
++field.stones_nearby_;
}
@@ -906,22 +1019,20 @@
// - there is an enemy
// Currently more military buildings are built then needed
// and "optimalization" (dismantling not needed buildings) is done afterwards
-bool DefaultAI::construct_building(int32_t gametime) { // (int32_t gametime)
+bool DefaultAI::construct_building(int32_t gametime) {
// Just used for easy checking whether a mine or something else was built.
bool mine = false;
bool field_blocked = false;
uint32_t consumers_nearby_count = 0;
std::vector<int32_t> spots_avail;
spots_avail.resize(4);
- // uint16_t const pn = player_number();
+ Map& map = game().map();
for (int32_t i = 0; i < 4; ++i)
spots_avail.at(i) = 0;
- for (std::list<BuildableField*>::iterator i = buildable_fields.begin();
- i != buildable_fields.end();
- ++i)
- ++spots_avail.at((*i)->coords.field->nodecaps() & BUILDCAPS_SIZEMASK);
+ for (const BuildableField* buildable_field : buildable_fields)
+ ++spots_avail.at(buildable_field->coords.field->nodecaps() & BUILDCAPS_SIZEMASK);
spots_ = spots_avail.at(BUILDCAPS_SMALL);
spots_ += spots_avail.at(BUILDCAPS_MEDIUM);
@@ -1011,15 +1122,15 @@
Coords proposed_coords;
// Remove outdated fields from blocker list
- for (std::list<BlockedField>::iterator i = blocked_fields.begin(); i != blocked_fields.end();)
- if (i->blocked_until_ < game().get_gametime()) {
- i = blocked_fields.erase(i);
- } else {
- ++i;
+ for (const BlockedField& blocked_field : blocked_fields) {
+ if (blocked_field.blocked_until_ < game().get_gametime()) {
+ blocked_fields.remove(blocked_field);
}
+ }
// testing big military buildings, whether critical construction
- // material is (not) needed
+ // material is available (at least in amount of
+ // 2/3 of default target amount)
for (uint32_t j = 0; j < buildings_.size(); ++j) {
BuildingObserver& bo = buildings_.at(j);
@@ -1037,19 +1148,13 @@
bo.build_material_shortage_ = false;
- for (EconomyObserver* observer : economies) {
- // Don't check if the economy has no warehouse.
- if (observer->economy.warehouses().empty()) {
- continue;
- }
-
- for (uint32_t m = 0; m < bo.critical_built_mat_.size(); ++m) {
- WareIndex wt(static_cast<size_t>(bo.critical_built_mat_.at(m)));
-
- if (observer->economy.needs_ware(wt)) {
- bo.build_material_shortage_ = true;
- continue;
- }
+ // checking we have enough critical material on stock
+ for (uint32_t m = 0; m < bo.critical_built_mat_.size(); ++m) {
+ WareIndex wt(static_cast<size_t>(bo.critical_built_mat_.at(m)));
+ // using default ware quantity, not the best solution but will do
+ if (get_warehoused_stock(wt) <
+ tribe_->get_ware_descr(wt)->default_target_quantity() * 2 / 3) {
+ bo.build_material_shortage_ = true;
}
}
}
@@ -1060,25 +1165,17 @@
int16_t max_needed_preciousness = 0; // preciousness_ of most precious NEEDED output
// first scan all buildable fields for regular buildings
- for (std::list<BuildableField*>::iterator i = buildable_fields.begin();
- i != buildable_fields.end();
- ++i) {
- BuildableField* const bf = *i;
+ for (const BuildableField* buildable_field : buildable_fields) {
- // if 'buildable field' update is overdue for more then 8 seconds
- // (= bf has not been updated for about 15 seconds)
- // skip the bf in evaluation, bacause information
- // contained in bf are too old
- if (bf->next_update_due_ < gametime - 8000) {
+ if (buildable_field->next_update_due_ < gametime - 8000) {
continue;
}
// Continue if field is blocked at the moment
field_blocked = false;
- for (std::list<BlockedField>::iterator j = blocked_fields.begin(); j != blocked_fields.end();
- ++j) {
- if (j->coords == bf->coords) {
+ for (const BlockedField& blocked_field : blocked_fields) {
+ if (blocked_field.coords == buildable_field->coords) {
field_blocked = true;
}
}
@@ -1089,7 +1186,7 @@
}
assert(player_);
- int32_t const maxsize = player_->get_buildcaps(bf->coords) & BUILDCAPS_SIZEMASK;
+ int32_t const maxsize = player_->get_buildcaps(buildable_field->coords) & BUILDCAPS_SIZEMASK;
// For every field test all buildings
for (uint32_t j = 0; j < buildings_.size(); ++j) {
@@ -1108,6 +1205,13 @@
continue;
}
+ // testing for reserved ports
+ if (!bo.is_port_) {
+ if (port_reserved_coords.count(coords_hash(buildable_field->coords)) > 0) {
+ continue;
+ }
+ }
+
if (time(nullptr) % 3 == 0 && bo.total_count() > 0) {
continue;
} // add randomnes and ease AI
@@ -1139,13 +1243,13 @@
if (bo.type == BuildingObserver::PRODUCTIONSITE) {
// exclude spots on border
- if (bf->near_border_ && !bo.need_trees_ && !bo.need_stones_ && !bo.is_fisher_) {
+ if (buildable_field->near_border_ && !bo.need_trees_ && !bo.need_stones_ && !bo.is_fisher_) {
continue;
}
// this can be only a well (as by now)
if (bo.mines_water_) {
- if (bf->ground_water_ < 2) {
+ if (buildable_field->ground_water_ < 2) {
continue;
}
@@ -1171,8 +1275,8 @@
if (bo.stocklevel_ > 50 + productionsites.size() * 5) {
continue;
}
- prio += bf->ground_water_ - 2;
- prio = recalc_with_border_range(*bf, prio);
+ prio += buildable_field->ground_water_ - 2;
+ prio = recalc_with_border_range(*buildable_field, prio);
} else if (bo.need_trees_) { // LUMBERJACS
@@ -1180,14 +1284,14 @@
3 + static_cast<int32_t>(mines_.size() + productionsites.size()) / 15;
if (bo.total_count() == 0) {
- prio = 500 + bf->trees_nearby_;
+ prio = 500 + buildable_field->trees_nearby_;
}
else if (bo.total_count() == 1) {
- prio = 400 + bf->trees_nearby_;
+ prio = 400 + buildable_field->trees_nearby_;
}
- else if (bf->trees_nearby_ < 2) {
+ else if (buildable_field->trees_nearby_ < 2) {
continue;
}
@@ -1199,15 +1303,15 @@
prio = 0;
}
- if (bf->producers_nearby_.at(bo.outputs_.at(0)) > 1) {
+ if (buildable_field->producers_nearby_.at(bo.outputs_.at(0)) > 1) {
continue;
}
- prio += 2 * bf->trees_nearby_ - 10 -
- bf->producers_nearby_.at(bo.outputs_.at(0)) * 5 -
+ prio += 2 * buildable_field->trees_nearby_ - 10 -
+ buildable_field->producers_nearby_.at(bo.outputs_.at(0)) * 5 -
new_buildings_stop_ * 15;
- if (bf->near_border_) {
+ if (buildable_field->near_border_) {
prio = prio / 2;
}
}
@@ -1220,7 +1324,7 @@
if (bo.cnt_under_construction_ > 0) {
continue;
}
- prio = bf->stones_nearby_;
+ prio = buildable_field->stones_nearby_;
if (prio <= 0) {
continue;
@@ -1240,14 +1344,14 @@
}
// to prevent to many quaries on one spot
- prio = prio - 50 * bf->producers_nearby_.at(bo.outputs_.at(0));
+ prio = prio - 50 * buildable_field->producers_nearby_.at(bo.outputs_.at(0));
- if (bf->near_border_) {
+ if (buildable_field->near_border_) {
prio = prio / 2;
}
} else if (bo.is_hunter_) {
- if (bf->critters_nearby_ < 5) {
+ if (buildable_field->critters_nearby_ < 5) {
continue;
}
@@ -1256,7 +1360,7 @@
}
prio +=
- (bf->critters_nearby_ * 2) - 8 - 5 * bf->producers_nearby_.at(bo.outputs_.at(0));
+ (buildable_field->critters_nearby_ * 2) - 8 - 5 * buildable_field->producers_nearby_.at(bo.outputs_.at(0));
} else if (bo.is_fisher_) { // fisher
@@ -1269,7 +1373,7 @@
continue;
}
- if (bf->water_nearby_ < 2) {
+ if (buildable_field->water_nearby_ < 2) {
continue;
}
@@ -1288,11 +1392,11 @@
continue;
}
- if (bf->producers_nearby_.at(bo.outputs_.at(0)) >= 1) {
+ if (buildable_field->producers_nearby_.at(bo.outputs_.at(0)) >= 1) {
continue;
}
- prio = bf->fish_nearby_ - new_buildings_stop_ * 15 * bo.total_count();
+ prio = buildable_field->fish_nearby_ - new_buildings_stop_ * 15 * bo.total_count();
} else if (bo.production_hint_ >= 0) {
// first setting targets (needed also for dismantling)
@@ -1311,7 +1415,7 @@
if (bo.plants_trees_) { // RANGERS
// if there are too many trees nearby
- if (bf->trees_nearby_ > 25 && bo.total_count() >= 1) {
+ if (buildable_field->trees_nearby_ > 25 && bo.total_count() >= 1) {
continue;
}
@@ -1339,12 +1443,12 @@
}
// considering near trees and producers
- prio += (30 - bf->trees_nearby_) * 2 +
- bf->producers_nearby_.at(bo.production_hint_) * 5 -
+ prio += (30 - buildable_field->trees_nearby_) * 2 +
+ buildable_field->producers_nearby_.at(bo.production_hint_) * 5 -
new_buildings_stop_ * 15;
// considering space consumers nearby
- prio -= bf->space_consumers_nearby_ * 5;
+ prio -= buildable_field->space_consumers_nearby_ * 5;
} else { // FISH BREEDERS and GAME KEEPERS
if (new_buildings_stop_ && bo.total_count() > 0) {
@@ -1352,11 +1456,11 @@
}
// especially for fish breeders
- if (bo.need_water_ && bf->water_nearby_ < 2) {
+ if (bo.need_water_ && buildable_field->water_nearby_ < 2) {
continue;
}
if (bo.need_water_) {
- prio += bf->water_nearby_ / 5;
+ prio += buildable_field->water_nearby_ / 5;
}
if (bo.total_count() > bo.cnt_target_) {
@@ -1373,14 +1477,14 @@
}
if (bo.total_count() == 0 && gametime > 45 * 1000) {
- prio += 100 + bf->producers_nearby_.at(bo.production_hint_) * 10;
- } else if (bf->producers_nearby_.at(bo.production_hint_) == 0) {
+ prio += 100 + buildable_field->producers_nearby_.at(bo.production_hint_) * 10;
+ } else if (buildable_field->producers_nearby_.at(bo.production_hint_) == 0) {
continue;
} else {
- prio += bf->producers_nearby_.at(bo.production_hint_) * 10;
+ prio += buildable_field->producers_nearby_.at(bo.production_hint_) * 10;
}
- if (bf->enemy_nearby_) {
+ if (buildable_field->enemy_nearby_) {
prio -= 10;
}
}
@@ -1405,6 +1509,10 @@
} else if (bo.cnt_built_ == 1 && game().get_gametime() > 40 * 60 * 1000 &&
bo.desc->enhancement() != INVALID_INDEX && !mines_.empty()) {
prio += 10;
+ } else if (bo.is_shipyard_) {
+ if (!seafaring_economy) {
+ continue;
+ }
} else if (!output_is_needed) {
continue;
} else if (bo.cnt_built_ == 0 && game().get_gametime() > 40 * 60 * 1000) {
@@ -1420,31 +1528,37 @@
prio += max_needed_preciousness + kDefaultPrioBoost;
if (bo.space_consumer_) { // need to consider trees nearby
- prio += 20 - (bf->trees_nearby_ / 3);
+ prio += 20 - (buildable_field->trees_nearby_ / 3);
}
// we attempt to cluster space consumers together
if (bo.space_consumer_) { // need to consider trees nearby
- prio += bf->space_consumers_nearby_ * 2;
+ prio += buildable_field->space_consumers_nearby_ * 2;
}
- if (bo.space_consumer_ && !bf->water_nearby_) { // not close to water
+ if (bo.space_consumer_ && !buildable_field->water_nearby_) { // not close to water
prio += 1;
}
if (bo.space_consumer_ &&
- !bf->unowned_mines_pots_nearby_) { // not close to mountains
+ !buildable_field->unowned_mines_pots_nearby_) { // not close to mountains
prio += 1;
}
if (!bo.space_consumer_) {
- prio -= bf->producers_nearby_.at(bo.outputs_.at(0)) * 20;
+ prio -= buildable_field->producers_nearby_.at(bo.outputs_.at(0)) * 20;
} // leave some free space between them
- prio -= bf->space_consumers_nearby_ * 3;
+ prio -= buildable_field->space_consumers_nearby_ * 3;
}
- if (!bo.inputs_.empty()) {
+ else if (bo.is_shipyard_) {
+ // for now AI builds only one shipyard
+ if (buildable_field->water_nearby_ > 3 && bo.total_count() == 0 && seafaring_economy) {
+ prio += kDefaultPrioBoost + productionsites.size() * 5 + buildable_field->water_nearby_;
+ }
+
+ } else if (!bo.inputs_.empty()) {
if (bo.total_count() == 0) {
prio += max_needed_preciousness + kDefaultPrioBoost;
}
@@ -1462,7 +1576,7 @@
consumers_nearby_count = 0;
for (size_t k = 0; k < bo.outputs_.size(); ++k)
- consumers_nearby_count += bf->consumers_nearby_.at(bo.outputs_.at(k));
+ consumers_nearby_count += buildable_field->consumers_nearby_.at(bo.outputs_.at(k));
if (consumers_nearby_count > 0) {
prio += 1;
@@ -1473,11 +1587,11 @@
// we allow 1 exemption from big buildings prohibition
if (bo.build_material_shortage_ &&
- (bo.cnt_under_construction_ > 0 || !(bf->enemy_nearby_))) {
+ (bo.cnt_under_construction_ > 0 || !(buildable_field->enemy_nearby_))) {
continue;
}
- if (!bf->unowned_land_nearby_) {
+ if (!buildable_field->unowned_land_nearby_) {
continue;
}
@@ -1489,21 +1603,21 @@
continue;
}
- if (expansion_mode == kDefenseOnly && !bf->enemy_nearby_) {
+ if (expansion_mode == kDefenseOnly && !buildable_field->enemy_nearby_) {
continue;
}
- if (bf->enemy_nearby_ && bo.fighting_type_) {
+ if (buildable_field->enemy_nearby_ && bo.fighting_type_) {
;
} // it is ok, go on
- else if (bf->unowned_mines_pots_nearby_ > 2 &&
+ else if (buildable_field->unowned_mines_pots_nearby_ > 2 &&
(bo.mountain_conqueror_ || bo.expansion_type_)) {
;
} // it is ok, go on
- else if (bf->unowned_land_nearby_ && bo.expansion_type_ &&
+ else if (buildable_field->unowned_land_nearby_ && bo.expansion_type_ &&
num_milit_constructionsites <= 1) {
; // we allow big buildings now
- } else if (bf->unowned_land_nearby_ &&
+ } else if (buildable_field->unowned_land_nearby_ &&
bo.expansion_type_) { // decreasing probability for big buidlings
if (bo.desc->get_size() == 2 && gametime % 15 >= 1) {
continue;
@@ -1518,8 +1632,8 @@
} // the building is not suitable for situation
// not to build so many military buildings nearby
- if (!bf->enemy_nearby_ &&
- (bf->military_in_constr_nearby_ + bf->military_unstationed_) > 0) {
+ if (!buildable_field->enemy_nearby_ &&
+ (buildable_field->military_in_constr_nearby_ + buildable_field->military_unstationed_) > 0) {
continue;
}
@@ -1529,14 +1643,14 @@
local_boost = 200;
}
- prio = (bf->unowned_land_nearby_ * 2 * resource_necessity_territory_ / 255 +
- bf->unowned_mines_pots_nearby_ * resource_necessity_mines_ / 255 +
- bf->stones_nearby_ / 2 + bf->military_loneliness_ / 10 - 60 + local_boost +
- bf->water_nearby_ * resource_necessity_water_ / 255);
+ prio = (buildable_field->unowned_land_nearby_ * 2 * resource_necessity_territory_ / 255 +
+ buildable_field->unowned_mines_pots_nearby_ * resource_necessity_mines_ / 255 +
+ buildable_field->stones_nearby_ / 2 + buildable_field->military_loneliness_ / 10 - 60 + local_boost +
+ buildable_field->water_nearby_ * resource_necessity_water_ / 255);
// special bonus due to remote water for atlanteans
if (resource_necessity_water_needed_)
- prio += bf->distant_water_ * resource_necessity_water_ / 255;
+ prio += buildable_field->distant_water_ * resource_necessity_water_ / 255;
if (bo.desc->get_size() < maxsize) {
prio = prio - 5;
@@ -1549,20 +1663,30 @@
// for expansion)
const int16_t bottom_treshold =
15 - ((virtual_mines <= 4) ? (5 - virtual_mines) * 2 : 0);
- if (bf->enemy_nearby_ && bf->military_capacity_ < bottom_treshold) {
- prio += 50 + (bottom_treshold - bf->military_capacity_) * 20;
+ if (buildable_field->enemy_nearby_ && buildable_field->military_capacity_ < bottom_treshold) {
+ prio += 50 + (bottom_treshold - buildable_field->military_capacity_) * 20;
}
- if (bf->enemy_nearby_ && bf->military_capacity_ > bottom_treshold + 4) {
- prio -= (bf->military_capacity_ - (bottom_treshold + 4)) * 5;
+ if (buildable_field->enemy_nearby_ && buildable_field->military_capacity_ > bottom_treshold + 4) {
+ prio -= (buildable_field->military_capacity_ - (bottom_treshold + 4)) * 5;
}
} else if (bo.type == BuildingObserver::WAREHOUSE) {
// exclude spots on border
- if (bf->near_border_) {
- continue;
- }
+ if (buildable_field->near_border_ && !bo.is_port_) {
+ continue;
+ }
+
+ if (!buildable_field->is_portspace_ && bo.is_port_) {
+ continue;
+ }
+
+ if (bo.cnt_under_construction_ > 0) {
+ continue;
+ }
+
+ bool warehouse_needed = false;
// Build one warehouse for ~every 35 productionsites and mines_.
// Militarysites are slightly important as well, to have a bigger
@@ -1572,64 +1696,107 @@
static_cast<int32_t>(numof_warehouses_) &&
bo.cnt_under_construction_ == 0) {
prio = 20;
- }
-
- // take care about borders and enemies
- if (bf->enemy_nearby_) {
- prio /= 2;
- }
-
- if (bf->unowned_land_nearby_) {
- prio /= 2;
- }
-
- // TODO(unknown): introduce check that there is no warehouse nearby
- // to prevent too close placing
+ warehouse_needed = true;
+ }
+
+ // but generally we prefer ports
+ if (bo.is_port_) {
+ prio += 10;
+ }
+
+ // special boost for first port
+ if (bo.is_port_ && bo.total_count() == 0 && productionsites.size() > 5 &&
+ !buildable_field->enemy_nearby_ && buildable_field->is_portspace_ && seafaring_economy) {
+ prio += kDefaultPrioBoost + productionsites.size();
+ warehouse_needed = true;
+ }
+
+ if (!warehouse_needed) {
+ continue;
+ }
+
+ // iterating over current warehouses and testing a distance
+ // getting distance to nearest warehouse and adding it to a score
+ uint16_t nearest_distance = std::numeric_limits<uint16_t>::max();
+ for (const WarehouseSiteObserver& wh_obs : warehousesites) {
+ const uint16_t actual_distance =
+ map.calc_distance(buildable_field->coords, wh_obs.site->get_position());
+ nearest_distance = std::min(nearest_distance, actual_distance);
+ }
+ //but limit to 15
+ const uint16_t max_distance_considered = 15;
+ nearest_distance = std::min(nearest_distance, max_distance_considered);
+ prio += nearest_distance;
+
+ // take care about and enemies
+ if (buildable_field->enemy_nearby_) {
+ prio /= 2;
+ }
+
+ if (buildable_field->unowned_land_nearby_ && !bo.is_port_) {
+ prio /= 2;
+ }
} else if (bo.type == BuildingObserver::TRAININGSITE) {
+ if (virtual_mines < 5) {
+ continue;
+ }
+
// exclude spots on border
- if (bf->near_border_) {
+ if (buildable_field->near_border_) {
continue;
}
- if (virtual_mines<3) {
+ if (virtual_mines < 3) {
continue;
}
// build after 20 production sites and then after each 50 production site
- if (static_cast<int32_t>((productionsites.size() + 30) / 50) > bo.total_count() &&
+ if (static_cast<int32_t>((productionsites.size() + 40) / 60) > bo.total_count() &&
bo.cnt_under_construction_ == 0) {
prio = 4 + kDefaultPrioBoost;
}
// take care about borders and enemies
- if (bf->enemy_nearby_) {
+ if (buildable_field->enemy_nearby_) {
prio /= 2;
}
- if (bf->unowned_land_nearby_) {
+ if (buildable_field->unowned_land_nearby_) {
prio /= 2;
}
}
// think of space consuming buildings nearby like farms or vineyards
- prio -= bf->space_consumers_nearby_ * 10;
+ prio -= buildable_field->space_consumers_nearby_ * 10;
// Stop here, if priority is 0 or less.
if (prio <= 0) {
continue;
}
+ // testing also vicinity
+ if (!bo.is_port_) {
+ if (port_reserved_coords.count(coords_hash(buildable_field->coords)) > 0) {
+ continue;
+ }
+ }
+
// Prefer road side fields
- prio += bf->preferred_ ? 1 : 0;
+ prio += buildable_field->preferred_ ? 1 : 0;
// don't waste good land for small huts
prio -= (maxsize - bo.desc->get_size()) * 5;
+ // prefer vicinity of ports (with exemption of warehouses)
+ if (buildable_field->port_nearby_ && bo.type == BuildingObserver::MILITARYSITE) {
+ prio *= 2;
+ }
+
if (prio > proposed_priority) {
best_building = &bo;
proposed_priority = prio;
- proposed_coords = bf->coords;
+ proposed_coords = buildable_field->coords;
}
} // ending loop over buildings
} // ending loop over fields
@@ -1687,18 +1854,16 @@
}
// iterating over fields
- for (std::list<MineableField*>::iterator j = mineable_fields.begin();
- j != mineable_fields.end();
- ++j) {
+ for (const MineableField* mineable_field : mineable_fields) {
- if ((*j)->coords.field->get_resources() != bo.mines_) {
+ if (mineable_field->coords.field->get_resources() != bo.mines_) {
continue;
}
- int32_t prio = (*j)->coords.field->get_resources_amount();
+ int32_t prio = mineable_field->coords.field->get_resources_amount();
// applying nearnes penalty
- prio = prio - (*j)->mines_nearby_ * nearness_penalty;
+ prio = prio - mineable_field->mines_nearby_ * nearness_penalty;
// Only build mines_ on locations where some material can be mined
if (prio < 2) {
@@ -1708,10 +1873,8 @@
// Continue if field is blocked at the moment
bool blocked = false;
- for (std::list<BlockedField>::iterator k = blocked_fields.begin();
- k != blocked_fields.end();
- ++k)
- if ((*j)->coords == k->coords) {
+ for (const BlockedField& blocked_field :blocked_fields)
+ if (mineable_field->coords == blocked_field.coords) {
blocked = true;
break;
}
@@ -1722,13 +1885,13 @@
}
// Prefer road side fields
- prio += (*j)->preferred_ ? 1 : 0;
+ prio += mineable_field->preferred_ ? 1 : 0;
if (prio > proposed_priority) {
// proposed_building = bo.id;
best_building = &bo;
proposed_priority = prio;
- proposed_coords = (*j)->coords;
+ proposed_coords = mineable_field->coords;
mine = true;
}
} // end of evaluation of field
@@ -1762,7 +1925,6 @@
block_time = 25 * 1000;
block_area = 6;
}
- Map& map = game().map();
MapRegion<Area<FCoords>> mr(map, Area<FCoords>(map.get_fcoords(proposed_coords), block_area));
do {
@@ -1924,10 +2086,10 @@
continue;
}
- std::vector<NearFlag>::iterator f =
+ std::vector<NearFlag>::iterator near_flag_it =
find(reachableflags.begin(), reachableflags.end(), queue.top().flag);
- if (f != reachableflags.end()) {
+ if (near_flag_it != reachableflags.end()) {
queue.pop();
continue;
}
@@ -1968,7 +2130,38 @@
// Increasing the failed_connection_tries counter
// At the same time it indicates a time an economy is without a warehouse
EconomyObserver* eco = get_economy_observer(flag.economy());
+
+ // there are two special situations which have a bit different treatment
+ bool is_remote_port_csite = false;
+ bool stationed_military = false;
if (flag.get_economy()->warehouses().empty()) {
+ // first very special case - lonesome port (in the phase of constructionsite)
+ // obviously it has no warehouse/road network to connect to
+ if (upcast(ConstructionSite const, constructionsite, flag.get_building())) {
+ BuildingObserver& bo = get_building_observer(constructionsite->building().name().c_str());
+ if (bo.is_port_ &&
+ remote_ports_coords.count(coords_hash(flag.get_building()->get_position())) > 0) {
+ is_remote_port_csite = true;
+ }
+ }
+
+ // second exemption is when a military buiding was conquered, it
+ // might be just too far from near connected building
+ if (Building* b = flag.get_building()) {
+ if (upcast(MilitarySite, militb, b)) {
+ if (militb->present_soldiers().size() > 0) {
+ stationed_military = true;
+ // also increasing checkradius a bit
+ checkradius += 4;
+ }
+ }
+ }
+ }
+
+ if (is_remote_port_csite ||
+ (stationed_military && game().get_gametime() % 10 > 0)) { // counter disabled
+ ;
+ } else if (flag.get_economy()->warehouses().empty()) {
eco->failed_connection_tries += 1;
} else {
eco->failed_connection_tries = 0;
@@ -2014,7 +2207,7 @@
for (const Coords& reachable_coords : reachable) {
- // first make sure there is an immovable (shold be, but still)
+ // first make sure there is an immovable (should be, but still)
if (upcast(PlayerImmovable const, player_immovable, map[reachable_coords].get_immovable())) {
// if it is the road, make a flag there
@@ -2034,7 +2227,7 @@
}
// now make sure that this field has not been processed yet
- const int32_t hash = reachable_coords.x << 16 | reachable_coords.y;
+ const uint32_t hash = coords_hash(reachable_coords);
if (lookuptable.count(hash) == 0) {
lookuptable.insert(hash);
@@ -2075,10 +2268,10 @@
// algorithm to walk on roads
while (!queue.empty()) {
- std::vector<NearFlag>::iterator f =
+ std::vector<NearFlag>::iterator near_flag_it =
find(nearflags_tmp.begin(), nearflags_tmp.end(), queue.top().flag);
- if (f != nearflags_tmp.end()) {
+ if (near_flag_it != nearflags_tmp.end()) {
queue.pop();
continue;
}
@@ -2113,21 +2306,16 @@
// iterating over nearflags_tmp, each item in nearflags_tmp should be contained also in nearflags
// so for each corresponding field in nearflags we update "cost" (distance on existing roads)
// to actual value
- for (std::vector<NearFlag>::iterator nf_walk_it = nearflags_tmp.begin();
- nf_walk_it != nearflags_tmp.end();
- ++nf_walk_it) {
- int32_t const hash_walk =
- nf_walk_it->flag->get_position().x << 16 | nf_walk_it->flag->get_position().y;
+ for (const NearFlag& temp_near_flag : nearflags_tmp) {
+ uint32_t const hash_walk = coords_hash(temp_near_flag.flag->get_position());
if (lookuptable.count(hash_walk) > 0) {
- // iterting over nearflags
- for (std::vector<NearFlag>::iterator nf_it = nearflags.begin(); nf_it != nearflags.end();
- ++nf_it) {
- int32_t const hash =
- nf_it->flag->get_position().x << 16 | nf_it->flag->get_position().y;
+ // iterating over nearflags
+ for (NearFlag& near_flag : nearflags) {
+ uint32_t const hash = coords_hash(near_flag.flag->get_position());
if (hash == hash_walk) {
// decreasing "cost" (of walking via roads)
- if (nf_it->cost_ > nf_walk_it->cost_) {
- nf_it->cost_ = nf_walk_it->cost_;
+ if (near_flag.cost_ > temp_near_flag.cost_) {
+ near_flag.cost_ = temp_near_flag.cost_;
}
}
}
@@ -2200,26 +2388,21 @@
get_economy_observer(flag.economy())->flags.push_back(&flag);
}
- for (std::list<EconomyObserver*>::iterator obs_iter = economies.begin();
- obs_iter != economies.end();
- ++obs_iter) {
+ for (EconomyObserver* economy_observer : economies) {
// check if any flag has changed its economy
- std::list<Flag const*>& fl = (*obs_iter)->flags;
+ std::list<Flag const*>& fl = economy_observer->flags;
- for (std::list<Flag const*>::iterator j = fl.begin(); j != fl.end();) {
- if (&(*obs_iter)->economy != &(*j)->economy()) {
- get_economy_observer((*j)->economy())->flags.push_back(*j);
- j = fl.erase(j);
- } else {
- ++j;
+ for (const Flag* flag : fl) {
+ if (&economy_observer->economy != &flag->economy()) {
+ get_economy_observer(flag->economy())->flags.push_back(flag);
+ fl.remove(flag);
}
}
// if there are no more flags in this economy,
// we no longer need it's observer
- if ((*obs_iter)->flags.empty()) {
- delete *obs_iter;
- economies.erase(obs_iter);
+ if (economy_observer->flags.empty()) {
+ economies.remove(economy_observer);
return true;
}
}
@@ -2488,7 +2671,7 @@
}
// remaining buildings without inputs and not supporting ones (fishers only left probably and
- // huters)
+ // hunters)
if (site.bo->inputs_.empty() && site.bo->production_hint_ < 0 &&
site.site->can_start_working() && !site.bo->space_consumer_ &&
@@ -2536,6 +2719,209 @@
return changed;
}
+// This function scans current situation with shipyards, ports, ships, ongoing expeditions
+// and makes two decisions:
+// - build a ship
+// - start preparation for expedition
+bool DefaultAI::marine_main_decisions(uint32_t const gametime) {
+ if (gametime < next_marine_decisions_due) {
+ return false;
+ }
+ next_marine_decisions_due += kMarineDecisionInterval;
+
+ if (!seafaring_economy) {
+ return false;
+ }
+
+ // getting some base statistics
+ player_ = game().get_player(player_number());
+ uint16_t ports_count = 0;
+ uint16_t shipyards_count = 0;
+ uint16_t working_shipyards_count = 0;
+ uint16_t expeditions_in_prep = 0;
+ uint16_t expeditions_in_progress = 0;
+ uint16_t territories_count = 1;
+ bool idle_shipyard_stocked = false;
+
+ // goes over all warehouses (these includes ports)
+ for (const WarehouseSiteObserver& wh_obs : warehousesites) {
+ if (wh_obs.bo->is_port_) {
+ ports_count += 1;
+ if (Widelands::PortDock* pd = wh_obs.site->get_portdock()) {
+ if (pd->expedition_started()) {
+ expeditions_in_prep += 1;
+ }
+ } else {
+ log(" there is a port without portdock at %3dx%3d?\n",
+ wh_obs.site->get_position().x,
+ wh_obs.site->get_position().y);
+ }
+ }
+ }
+
+ // goes over productionsites and gets status of shipyards
+ for (const ProductionSiteObserver& ps_obs : productionsites) {
+ if (ps_obs.bo->is_shipyard_) {
+ shipyards_count += 1;
+ if (!ps_obs.site->is_stopped()) {
+ working_shipyards_count += 1;
+ }
+ // counting stocks
+ uint8_t stocked_wares = 0;
+ std::vector<WaresQueue*> const warequeues = ps_obs.site->warequeues();
+ size_t const nr_warequeues = warequeues.size();
+ for (size_t i = 0; i < nr_warequeues; ++i) {
+ stocked_wares += warequeues[i]->get_filled();
+ }
+ if (stocked_wares == 16 && ps_obs.site->is_stopped()) {
+ idle_shipyard_stocked = true;
+ }
+ }
+ }
+
+ // and now over ships
+ for (const ShipObserver& ship_observer : allships) {
+ if (ship_observer.ship->state_is_expedition()) {
+ expeditions_in_progress += 1;
+ }
+ }
+
+ // we must verify that all remote ports are still ours (and exists at all)
+ bool still_ours;
+ for (std::unordered_set<uint32_t>::iterator ports_it = remote_ports_coords.begin();
+ ports_it != remote_ports_coords.end();
+ ++ports_it) {
+ still_ours = false;
+ FCoords fcoords = game().map().get_fcoords(coords_unhash(*ports_it));
+ if (fcoords.field->get_owned_by() == player_number()) {
+ if (upcast(PlayerImmovable, imm, fcoords.field->get_immovable())) {
+ still_ours = true;
+ }
+ }
+
+ if (!still_ours) {
+ remote_ports_coords.erase(*ports_it);
+ break;
+ }
+ }
+ territories_count += remote_ports_coords.size();
+
+ enum class FleetStatus: uint8_t {kNeedShip = 0, kEnoughShips = 1, kDoNothing = 2 };
+
+ // now we must compare ports vs ships to decide if new ship is needed or new expedition can start
+ FleetStatus enough_ships = FleetStatus::kDoNothing;
+ if (static_cast<float>(allships.size()) >
+ static_cast<float>((territories_count - 1) * 0.6 + ports_count * 0.75)) {
+ enough_ships = FleetStatus::kEnoughShips;
+ } else if (static_cast<float>(allships.size()) <
+ static_cast<float>((territories_count - 1) * 0.6 + ports_count * 0.75)) {
+ enough_ships = FleetStatus::kNeedShip;
+ }
+
+ // building a ship? if yes, find a shipyard and order it to build a ship
+ if (shipyards_count > 0 && enough_ships == FleetStatus::kNeedShip && idle_shipyard_stocked &&
+ ports_count > 0) {
+
+ for (const ProductionSiteObserver& ps_obs : productionsites) {
+ if (ps_obs.bo->is_shipyard_ && ps_obs.site->can_start_working() &&
+ ps_obs.site->is_stopped()) {
+ // make sure it is fully stocked
+ // counting stocks
+ uint8_t stocked_wares = 0;
+ std::vector<WaresQueue*> const warequeues = ps_obs.site->warequeues();
+ size_t const nr_warequeues = warequeues.size();
+ for (size_t i = 0; i < nr_warequeues; ++i) {
+ stocked_wares += warequeues[i]->get_filled();
+ }
+ if (stocked_wares < 16) {
+ continue;
+ }
+
+ game().send_player_start_stop_building(*ps_obs.site);
+ return true;
+ }
+ }
+ }
+
+ // starting an expedition? if yes, find a port and order it to start an expedition
+ if (ports_count > 0 && enough_ships == FleetStatus::kEnoughShips && expeditions_in_prep == 0 &&
+ expeditions_in_progress == 0) {
+ // we need to find a port
+ for (const WarehouseSiteObserver& wh_obs : warehousesites) {
+
+ if (wh_obs.bo->is_port_) {
+ game().send_player_start_or_cancel_expedition(*wh_obs.site);
+ return true;
+ }
+ }
+ }
+ return true;
+}
+
+// This identifies ships that are waiting for command
+bool DefaultAI::check_ships(uint32_t const gametime) {
+ if (gametime < next_ship_check_due) {
+ return false;
+ }
+
+ next_ship_check_due += kShipCheckInterval;
+
+ if (!seafaring_economy) {
+ return false;
+ }
+
+ if (!allships.empty()) {
+ // Iterating over ships and executing what is needed.
+ for (ShipObserver& ship_observer : allships) {
+ // Only two states need attention.
+ if ((ship_observer.ship->get_ship_state() == Widelands::Ship::EXP_WAITING ||
+ ship_observer.ship->get_ship_state() == Widelands::Ship::EXP_FOUNDPORTSPACE) &&
+ !ship_observer.waiting_for_command_) {
+ if (gametime - ship_observer.last_command_time > 180 * 1000) {
+ ship_observer.waiting_for_command_ = true;
+ log(" %1d: last command for ship at %3dx%3d was %3d seconds ago, something wrong "
+ "here?...\n",
+ player_number(),
+ ship_observer.ship->get_position().x,
+ ship_observer.ship->get_position().y,
+ (gametime - ship_observer.last_command_time) / 1000);
+ }
+ }
+ // If ship is waiting for command.
+ if (ship_observer.waiting_for_command_) {
+ expedition_management(ship_observer);
+ }
+ }
+ }
+
+ // processing marineTaskQueue_
+ while (!marineTaskQueue_.empty()) {
+ if (marineTaskQueue_.back() == kStopShipyard) {
+ // iterate over all production sites searching for shipyard
+ for (const ProductionSiteObserver& prod_site_observer : productionsites) {
+ if (prod_site_observer.bo->is_shipyard_) {
+ if (!prod_site_observer.site->is_stopped()) {
+ game().send_player_start_stop_building(*prod_site_observer.site);
+ }
+ }
+ }
+ }
+
+ if (marineTaskQueue_.back() == kReprioritize) {
+ for (const ProductionSiteObserver& prod_site_observer : productionsites) {
+ if (prod_site_observer.bo->is_shipyard_) {
+ for (uint32_t k = 0; k < prod_site_observer.bo->inputs_.size(); ++k) {
+ game().send_player_set_ware_priority(
+ *prod_site_observer.site, wwWARE, prod_site_observer.bo->inputs_.at(k), HIGH_PRIORITY);
+ }
+ }
+ }
+ }
+ marineTaskQueue_.pop_back();
+ }
+ return true;
+}
+
/**
* checks the first mine in list, takes care if it runs out of
* resources and finally reenqueues it at the end of the list.
@@ -2640,7 +3026,7 @@
int16_t* max_preciousness,
int16_t* max_needed_preciousness) {
- // reseting values
+ // resetting values
*output_is_needed = false;
*max_preciousness = 0;
*max_needed_preciousness = 0;
@@ -2709,6 +3095,43 @@
return count;
}
+// counts produced output in warehouses (only)
+// perhaps it will be able to replace get_stocklevel
+uint32_t DefaultAI::get_warehoused_stock(WareIndex wt) {
+ uint32_t count = 0;
+ for (const WarehouseSiteObserver& warehouse_observer : warehousesites) {
+ count += warehouse_observer.site->get_wares().stock(wt);
+ }
+ return count;
+}
+
+// this function only manipulates with trainingsites' inputs priority
+// decreases it when too many unoccupied military buildings
+bool DefaultAI::check_trainingsites(int32_t gametime) {
+ if (next_trainingsites_check_due_ > gametime) {
+ return false;
+ }
+ if (!trainingsites.empty()) {
+ next_trainingsites_check_due_ = gametime + kTrainingSitesCheckInterval;
+ } else {
+ next_trainingsites_check_due_ = gametime + 3 * kTrainingSitesCheckInterval;
+ }
+
+ uint8_t new_priority = DEFAULT_PRIORITY;
+ if (unstationed_milit_buildings_ > 2) {
+ new_priority = LOW_PRIORITY;
+ } else {
+ new_priority = DEFAULT_PRIORITY;
+ }
+ for (const TrainingSiteObserver& trainingsite_observer :trainingsites) {
+ for (uint32_t k = 0; k < trainingsite_observer.bo->inputs_.size(); ++k) {
+ game().send_player_set_ware_priority(
+ *trainingsite_observer.site, wwWARE, trainingsite_observer.bo->inputs_.at(k), new_priority);
+ }
+ }
+ return true;
+}
+
/**
* Updates the first military building in list and reenques it at the end of
* the list afterwards. If a militarysite is in secure area but holds more than
@@ -2728,10 +3151,8 @@
// construction
unstationed_milit_buildings_ = 0;
- for (std::list<MilitarySiteObserver>::iterator it = militarysites.begin();
- it != militarysites.end();
- ++it) {
- if (it->site->stationed_soldiers().size() == 0) {
+ for (const MilitarySiteObserver& militarysite_observer : militarysites) {
+ if (militarysite_observer.site->stationed_soldiers().size() == 0) {
unstationed_milit_buildings_ += 1;
}
}
@@ -2929,9 +3350,11 @@
/// \returns the economy observer containing \arg economy
EconomyObserver* DefaultAI::get_economy_observer(Economy& economy) {
- for (std::list<EconomyObserver*>::iterator i = economies.begin(); i != economies.end(); ++i)
- if (&(*i)->economy == &economy)
- return *i;
+ for (EconomyObserver* economy_observer : economies) {
+ if (&economy_observer->economy == &economy) {
+ return economy_observer;
+ }
+ }
economies.push_front(new EconomyObserver(economy));
return economies.front();
@@ -2969,20 +3392,16 @@
lose_building(*building);
} else if (upcast(Flag const, flag, &pi)) {
for (EconomyObserver* eco_obs : economies) {
- for (std::list<Flag const*>::iterator flag_iter = eco_obs->flags.begin();
- flag_iter != eco_obs->flags.end();
- ++flag_iter) {
- if (*flag_iter == flag) {
- eco_obs->flags.erase(flag_iter);
+ for (const Flag* economy_flag : eco_obs->flags) {
+ if (economy_flag == flag) {
+ eco_obs->flags.remove(economy_flag);
return;
}
}
}
- for (std::list<Flag const*>::iterator flag_iter = new_flags.begin();
- flag_iter != new_flags.end();
- ++flag_iter) {
- if (*flag_iter == flag) {
- new_flags.erase(flag_iter);
+ for (const Flag* new_flag : new_flags) {
+ if (new_flag == flag) {
+ new_flags.remove(new_flag);
return;
}
}
@@ -2995,20 +3414,217 @@
void DefaultAI::out_of_resources_site(const ProductionSite& site) {
// we must identify which mine matches the productionsite a note reffers to
- for (std::list<ProductionSiteObserver>::iterator i = mines_.begin(); i != mines_.end(); ++i)
- if (i->site == &site) {
- i->no_resources_count += 1;
+ for (ProductionSiteObserver& mine_observer : mines_)
+ if (mine_observer.site == &site) {
+ mine_observer.no_resources_count += 1;
break;
}
}
+// this scores spot for potential colony
+uint8_t DefaultAI::spot_scoring(Widelands::Coords candidate_spot) {
+
+ uint8_t score = 0;
+ uint16_t mineable_fields_count = 0;
+ Map& map = game().map();
+ // first making sure there are no other players nearby
+ std::list<uint32_t> queue;
+ std::unordered_set<uint32_t> done;
+ queue.push_front(coords_hash(candidate_spot));
+ while (!queue.empty()) {
+
+ // if already processed
+ if (done.count(queue.front()) > 0) {
+ queue.pop_front();
+ continue;
+ }
+
+ done.insert(queue.front());
+
+ Coords tmp_coords = coords_unhash(queue.front());
+
+ // if beyond range
+ if (map.calc_distance(candidate_spot, tmp_coords) > colony_scan_area_) {
+ continue;
+ }
+
+ Field* f = map.get_fcoords(tmp_coords).field;
+
+ // if owned by someone:
+ if (f->get_owned_by() > 0) {
+ // just return 0 as score
+ return 0;
+ }
+
+ // not interested if not walkable
+ if (!(f->nodecaps() & MOVECAPS_WALK)) {
+ continue;
+ }
+
+ // increase mines counter
+ if (f->nodecaps() & BUILDCAPS_MINE) {
+ mineable_fields_count += 1;
+ };
+
+ // add neighbours to a queue (duplicates are no problem)
+ // to relieve AI/CPU we skip every second field in each direction
+ // obstacles are usually wider then one field
+ for (Direction dir = FIRST_DIRECTION; dir <= LAST_DIRECTION; ++dir) {
+ Coords neigh_coords1;
+ map.get_neighbour(tmp_coords, dir, &neigh_coords1);
+ Coords neigh_coords2;
+ map.get_neighbour(neigh_coords1, dir, &neigh_coords2);
+ queue.push_front(coords_hash(neigh_coords2));
+ }
+ }
+
+ // if the island is too small
+ if (done.size() < 50) {
+ return 0;
+ }
+
+ // if we are here we put score
+ score = 1;
+ if (mineable_fields_count > 0) {
+ score += 1;
+ }
+
+ // here we check for surface stones + trees
+ std::vector<ImmovableFound> immovables;
+ // Search in a radius of range
+ map.find_immovables(Area<FCoords>(map.get_fcoords(candidate_spot), 10), &immovables);
+
+ int32_t const stone_attr = MapObjectDescr::get_attribute_id("granite");
+ uint16_t stones = 0;
+ int32_t const tree_attr = MapObjectDescr::get_attribute_id("tree");
+ uint16_t trees = 0;
+
+ for (uint32_t j = 0; j < immovables.size(); ++j) {
+ if (immovables.at(j).object->has_attribute(stone_attr)) {
+ ++stones;
+ }
+ if (immovables.at(j).object->has_attribute(tree_attr)) {
+ ++trees;
+ }
+ }
+ if (stones > 1) {
+ score += 1;
+ }
+ if (trees > 1) {
+ score += 1;
+ }
+
+ return score;
+}
+
+// this is called whenever ship received a notification that requires
+// navigation decisions (these notifiation are processes not in 'real time')
+void DefaultAI::expedition_management(ShipObserver& so) {
+
+ Map& map = game().map();
+ const int32_t gametime = game().get_gametime();
+
+ // first we put current spot into visited_spots_
+ bool first_time_here = false;
+ if (so.visited_spots_.count(coords_hash(so.ship->get_position())) == 0) {
+ first_time_here = true;
+ so.visited_spots_.insert(coords_hash(so.ship->get_position()));
+ }
+
+ // If we have portspace following options are avaiable:
+ // 1. Build a port there
+ if (so.ship->exp_port_spaces()->size() > 0) { // making sure we have possible portspaces
+
+ // we score the place
+ const uint8_t spot_score = spot_scoring(so.ship->exp_port_spaces()->front());
+
+ if ((gametime / 10) % 8 < spot_score) { // we build a port here
+ const Coords last_portspace = so.ship->exp_port_spaces()->front();
+ remote_ports_coords.insert(coords_hash(last_portspace));
+ game().send_player_ship_construct_port(*so.ship, so.ship->exp_port_spaces()->front());
+ so.last_command_time = gametime;
+ so.waiting_for_command_ = false;
+ // blocking the area for some time to save AI from idle attempts to built there
+ // buildings
+ // TODO(TiborB): how long it takes to build a port?
+ // I used 5 minutes
+ MapRegion<Area<FCoords>> mr(
+ game().map(), Area<FCoords>(map.get_fcoords(so.ship->exp_port_spaces()->front()), 8));
+ do {
+ BlockedField blocked2(
+ map.get_fcoords(*(mr.location().field)), gametime + 5 * 60 * 1000);
+ blocked_fields.push_back(blocked2);
+ } while (mr.advance(map));
+
+ return;
+ }
+
+ // decreasing colony_scan_area_
+ if (colony_scan_area_ > 15 && gametime % 10 == 0) {
+ colony_scan_area_ -= 1;
+ }
+ }
+
+ // if we are here, port was not ordered above
+ // 2. Go on with expedition
+
+ if (first_time_here) {
+ game().send_player_ship_explore_island(*so.ship, so.island_circ_direction);
+ so.last_command_time = gametime;
+ so.waiting_for_command_ = false;
+
+ // we was here but to add randomnes we might continue with expedition
+ } else if (gametime % 2 == 0) {
+ game().send_player_ship_explore_island(*so.ship, so.island_circ_direction);
+ so.last_command_time = gametime;
+ so.waiting_for_command_ = false;
+ } else {
+ // get swimable directions
+ std::vector<Direction> possible_directions;
+ for (Direction dir = FIRST_DIRECTION; dir <= LAST_DIRECTION; ++dir) {
+
+ // testing distance of 8 fields
+ // this would say there is an 'open sea' there
+ Widelands::FCoords tmp_fcoords = map.get_fcoords(so.ship->get_position());
+ for (int8_t i = 0; i < 8; ++i) {
+ tmp_fcoords = map.get_neighbour(tmp_fcoords, dir);
+ if (tmp_fcoords.field->nodecaps() & MOVECAPS_SWIM) {
+ if (i == 7) {
+ possible_directions.push_back(dir);
+ break; // not needed but.....
+ }
+ } else {
+ break;
+ }
+ }
+ }
+
+ // we test if there is open sea
+ if (possible_directions.empty()) {
+ // 2.A No there is no open sea
+ game().send_player_ship_explore_island(*so.ship, so.island_circ_direction);
+ so.last_command_time = gametime;
+ so.waiting_for_command_ = false;
+ ;
+ } else {
+ // 2.B Yes, pick one of avaiable directions
+ const Direction final_direction =
+ possible_directions.at(gametime % possible_directions.size());
+ game().send_player_ship_scout_direction(*so.ship, final_direction);
+ so.last_command_time = gametime;
+ so.waiting_for_command_ = false;
+ }
+ }
+ return;
+}
+
// this is called whenever we gain a new building
void DefaultAI::gain_building(Building& b) {
BuildingObserver& bo = get_building_observer(b.descr().name().c_str());
if (bo.type == BuildingObserver::CONSTRUCTIONSITE) {
BuildingObserver& target_bo =
- get_building_observer(dynamic_cast<ConstructionSite&>(b).building().name().c_str());
+ get_building_observer(dynamic_cast<const ConstructionSite&>(b).building().name().c_str());
++target_bo.cnt_under_construction_;
++num_constructionsites_;
if (target_bo.type == BuildingObserver::PRODUCTIONSITE) {
@@ -3031,6 +3647,10 @@
productionsites.back().unoccupied_till_ = game().get_gametime();
productionsites.back().stats_zero_ = 0;
productionsites.back().no_resources_count = 0;
+ if (bo.is_shipyard_) {
+ marineTaskQueue_.push_back(kStopShipyard);
+ marineTaskQueue_.push_back(kReprioritize);
+ }
for (uint32_t i = 0; i < bo.outputs_.size(); ++i)
++wares.at(bo.outputs_.at(i)).producers_;
@@ -3055,8 +3675,19 @@
militarysites.back().checks = bo.desc->get_size();
militarysites.back().enemies_nearby_ = true;
+ } else if (bo.type == BuildingObserver::TRAININGSITE) {
+ trainingsites.push_back(TrainingSiteObserver());
+ trainingsites.back().site = &dynamic_cast<TrainingSite&>(b);
+ trainingsites.back().bo = &bo;
+
} else if (bo.type == BuildingObserver::WAREHOUSE) {
++numof_warehouses_;
+ warehousesites.push_back(WarehouseSiteObserver());
+ warehousesites.back().site = &dynamic_cast<Warehouse&>(b);
+ warehousesites.back().bo = &bo;
+ if (bo.is_port_) {
+ ++num_ports;
+ }
}
}
}
@@ -3066,8 +3697,8 @@
BuildingObserver& bo = get_building_observer(b.descr().name().c_str());
if (bo.type == BuildingObserver::CONSTRUCTIONSITE) {
- BuildingObserver& target_bo = get_building_observer(
- dynamic_cast<const ConstructionSite&>(b).building().name().c_str());
+ BuildingObserver& target_bo =
+ get_building_observer(dynamic_cast<const ConstructionSite&>(b).building().name().c_str());
--target_bo.cnt_under_construction_;
--num_constructionsites_;
if (target_bo.type == BuildingObserver::PRODUCTIONSITE) {
@@ -3082,11 +3713,11 @@
if (bo.type == BuildingObserver::PRODUCTIONSITE) {
- for (std::list<ProductionSiteObserver>::iterator i = productionsites.begin();
- i != productionsites.end();
- ++i)
- if (i->site == &b) {
- productionsites.erase(i);
+ for (std::list<ProductionSiteObserver>::iterator prod_site_observer_it = productionsites.begin();
+ prod_site_observer_it != productionsites.end();
+ ++prod_site_observer_it)
+ if (prod_site_observer_it->site == &b) {
+ productionsites.erase(prod_site_observer_it);
break;
}
@@ -3098,10 +3729,10 @@
--wares.at(bo.inputs_.at(i)).consumers_;
}
} else if (bo.type == BuildingObserver::MINE) {
- for (std::list<ProductionSiteObserver>::iterator i = mines_.begin(); i != mines_.end();
- ++i) {
- if (i->site == &b) {
- mines_.erase(i);
+ for (std::list<ProductionSiteObserver>::iterator mines_observer_it = mines_.begin(); mines_observer_it != mines_.end();
+ ++mines_observer_it) {
+ if (mines_observer_it->site == &b) {
+ mines_.erase(mines_observer_it);
break;
}
}
@@ -3115,17 +3746,39 @@
}
} else if (bo.type == BuildingObserver::MILITARYSITE) {
- for (std::list<MilitarySiteObserver>::iterator i = militarysites.begin();
- i != militarysites.end();
- ++i) {
- if (i->site == &b) {
- militarysites.erase(i);
+ for (std::list<MilitarySiteObserver>::iterator militarysite_observer_it = militarysites.begin();
+ militarysite_observer_it != militarysites.end();
+ ++militarysite_observer_it) {
+ if (militarysite_observer_it->site == &b) {
+ militarysites.erase(militarysite_observer_it);
+ break;
+ }
+ }
+ } else if (bo.type == BuildingObserver::TRAININGSITE) {
+
+ for (std::list<TrainingSiteObserver>::iterator trainingsite_observer_it = trainingsites.begin();
+ trainingsite_observer_it != trainingsites.end();
+ ++trainingsite_observer_it) {
+ if (trainingsite_observer_it->site == &b) {
+ trainingsites.erase(trainingsite_observer_it);
break;
}
}
} else if (bo.type == BuildingObserver::WAREHOUSE) {
assert(numof_warehouses_ > 0);
--numof_warehouses_;
+ if (bo.is_port_) {
+ --num_ports;
+ }
+
+ for (std::list<WarehouseSiteObserver>::iterator warehouse_observer_it = warehousesites.begin();
+ warehouse_observer_it != warehousesites.end();
+ ++warehouse_observer_it) {
+ if (warehouse_observer_it->site == &b) {
+ warehousesites.erase(warehouse_observer_it);
+ break;
+ }
+ }
}
}
@@ -3134,7 +3787,7 @@
}
// Checks that supply line exists for given building.
-// Recurcsively verify that all inputs_ have a producer.
+// Recursively verify that all inputs_ have a producer.
// TODO(unknown): this function leads to periodic freezes of ~1 second on big games on my system.
// TODO(unknown): It needs profiling and optimization.
// NOTE: This is not needed anymore and it seems it is not missed neither
@@ -3165,7 +3818,7 @@
bool DefaultAI::consider_attack(int32_t const gametime) {
- // we presume that we are not attacking so we extend waitperiod
+ // we assume that we are not attacking so we extend waitperiod
// in case of attack the variable will be decreased below
// this is intended to save some CPU and add randomness in attacking
// and also differentiate according to type
@@ -3248,13 +3901,13 @@
if (genstats.at(j - 1).miltary_strength.empty()) {
log("ComputerPlayer(%d): miltary_strength is empty\n", player_number());
player_attackable.at(j - 1) = false;
- // Avoid division by zero
+ // Avoid division by zero
} else if (genstats.at(j - 1).miltary_strength.back() == 0) {
player_attackable.at(j - 1) = true;
any_attackable = true;
- // Check threshold
+ // Check threshold
} else if ((genstats.at(pn - 1).miltary_strength.back() * 100 /
- genstats.at(j - 1).miltary_strength.back()) > treshold_ratio) {
+ genstats.at(j - 1).miltary_strength.back()) > treshold_ratio) {
player_attackable.at(j - 1) = true;
any_attackable = true;
} else {
@@ -3262,7 +3915,8 @@
}
} catch (const std::out_of_range&) {
log("ComputerPlayer(%d): genstats entry missing - size :%d\n",
- player_number(), static_cast<unsigned int>(genstats.size()));
+ player_number(),
+ static_cast<unsigned int>(genstats.size()));
player_attackable.at(j - 1) = false;
}
}
@@ -3288,13 +3942,13 @@
for (uint32_t position = gametime % test_every; position < militarysites.size();
position += test_every) {
- // checked_own_ms_tmp += 1;
- std::list<MilitarySiteObserver>::iterator mso = militarysites.begin();
- std::advance(mso, position);
-
- MilitarySite* ms = mso->site;
-
- if (!mso->enemies_nearby_) {
+
+ std::list<MilitarySiteObserver>::iterator militarysite_observer_it = militarysites.begin();
+ std::advance(militarysite_observer_it, position);
+
+ MilitarySite* ms = militarysite_observer_it->site;
+
+ if (!militarysite_observer_it->enemies_nearby_) {
continue;
}
@@ -3308,10 +3962,8 @@
for (uint32_t j = 0; j < immovables.size(); ++j) {
// skip if in irrelevant_immovables
- const uint32_t hash = immovables.at(j).coords.x << 16 | immovables.at(j).coords.y;
- if (irrelevant_immovables.count(hash) > 0) {
- continue;
- } else {
+ const uint32_t hash = coords_hash(immovables.at(j).coords);
+ if (irrelevant_immovables.count(hash) == 0) {
irrelevant_immovables.insert(hash);
}
@@ -3334,7 +3986,6 @@
continue;
}
- // target_buildings.push_back(immovables.at(j));
const int32_t soldiers_difference =
player_->find_attack_soldiers(bld->base_flag()) - bld->present_soldiers().size();
@@ -3379,7 +4030,7 @@
}
}
- // we allways try to attack warehouse first
+ // we always try to attack warehouse first
if (best_wh_target != nullptr && gametime % 2 == 0) {
// attacking with all attack-ready soldiers
int32_t attackers = player_->find_attack_soldiers(best_wh_target->base_flag());
@@ -3413,6 +4064,34 @@
}
}
+// This runs once in 15 minutes, and adjust wares targets based on number of
+// productionsites and ports
+void DefaultAI::review_wares_targets(int32_t const gametime) {
+
+ player_ = game().get_player(player_number());
+ tribe_ = &player_->tribe();
+
+ // to avoid floats real multiplicator is multiplicator/10
+ uint16_t multiplicator = 10;
+ if ((productionsites.size() + num_ports * 5) > 50) {
+ multiplicator = (productionsites.size() + num_ports * 5) / 5;
+ }
+
+ for (EconomyObserver* observer : economies) {
+ WareIndex nritems = observer->economy.owner().tribe().get_nrwares();
+ for (Widelands::WareIndex id = 0; id < nritems; ++id) {
+ const uint16_t default_target = tribe_->get_ware_descr(id)->default_target_quantity();
+
+ game().send_player_command(*new Widelands::CmdSetWareTargetQuantity(
+ gametime,
+ player_number(),
+ player_->get_economy_number(&observer->economy),
+ id,
+ default_target * multiplicator / 10));
+ }
+ }
+}
+
// This is used for profiling, so usually this is not used :)
void DefaultAI::print_land_stats() {
// this will just print statistics of land size
=== modified file 'src/ai/defaultai.h'
--- src/ai/defaultai.h 2014-10-17 19:13:39 +0000
+++ src/ai/defaultai.h 2014-12-20 20:51:12 +0000
@@ -22,11 +22,14 @@
#include <map>
#include <memory>
+#include <unordered_set>
+
#include "ai/ai_help_structs.h"
#include "ai/computer_player.h"
#include "base/i18n.h"
#include "logic/immovable.h"
+#include "logic/ship.h"
namespace Widelands {
struct Road;
@@ -130,6 +133,18 @@
bool construct_building(int32_t);
+ uint32_t coords_hash(Widelands::Coords coords) {
+ uint32_t hash = coords.x << 16 | coords.y;
+ return hash;
+ }
+
+ Widelands::Coords coords_unhash(uint32_t hash) {
+ Widelands::Coords coords;
+ coords.x = hash >> 16; // is cast needed here???
+ coords.y = hash;
+ return coords;
+ }
+
// all road management is invoked by function improve_roads()
// if needed it calls create_shortcut_road() with a flag from which
// new road should be considered (or is needed)
@@ -139,18 +154,25 @@
bool dispensable_road_test(const Widelands::Road&);
bool check_economies();
bool check_productionsites(int32_t);
+ bool check_trainingsites(int32_t);
bool check_mines_(int32_t);
bool check_militarysites(int32_t);
+ bool marine_main_decisions(uint32_t);
+ bool check_ships(uint32_t);
uint32_t get_stocklevel_by_hint(size_t);
uint32_t get_stocklevel(BuildingObserver&);
+ uint32_t get_warehoused_stock(Widelands::WareIndex wt);
uint32_t get_stocklevel(Widelands::WareIndex); // count all direct outputs_
void check_helpersites(int32_t);
+ void review_wares_targets(int32_t);
int32_t recalc_with_border_range(const BuildableField&, int32_t);
int32_t calculate_need_for_ps(BuildingObserver&, int32_t);
void
consider_productionsite_influence(BuildableField&, Widelands::Coords, const BuildingObserver&);
+ // considering wood, stones, mines, water, fishes for candidate for colonization (new port)
+ uint8_t spot_scoring(Widelands::Coords candidate_spot);
EconomyObserver* get_economy_observer(Widelands::Economy&);
BuildingObserver& get_building_observer(char const*);
@@ -159,6 +181,7 @@
void lose_immovable(const Widelands::PlayerImmovable&);
void gain_building(Widelands::Building&);
void lose_building(const Widelands::Building&);
+ void expedition_management(ShipObserver&);
void out_of_resources_site(const Widelands::ProductionSite&);
bool check_supply(const BuildingObserver&);
@@ -181,10 +204,14 @@
uint32_t num_constructionsites_;
uint32_t num_milit_constructionsites;
uint32_t num_prod_constructionsites;
+ uint32_t num_ports;
std::list<Widelands::FCoords> unusable_fields;
std::list<BuildableField*> buildable_fields;
std::list<BlockedField> blocked_fields;
+ std::unordered_set<uint32_t> port_reserved_coords;
+ // to distinquish which ports are on home teritory and which one are remote
+ std::unordered_set<uint32_t> remote_ports_coords;
std::list<MineableField*> mineable_fields;
std::list<Widelands::Flag const*> new_flags;
std::list<Widelands::Coords> flags_to_be_removed;
@@ -193,6 +220,9 @@
std::list<ProductionSiteObserver> productionsites;
std::list<ProductionSiteObserver> mines_;
std::list<MilitarySiteObserver> militarysites;
+ std::list<WarehouseSiteObserver> warehousesites;
+ std::list<TrainingSiteObserver> trainingsites;
+ std::list<ShipObserver> allships;
std::vector<WareObserver> wares;
@@ -203,9 +233,12 @@
int32_t next_productionsite_check_due_;
int32_t next_mine_check_due_;
int32_t next_militarysite_check_due_;
+ uint32_t next_ship_check_due;
+ uint32_t next_marine_decisions_due;
int32_t next_attack_consideration_due_;
- int32_t next_helpersites_check_due_;
+ int32_t next_trainingsites_check_due_;
int32_t next_bf_check_due_;
+ int32_t next_wares_review_due_;
int32_t inhibit_road_building_;
int32_t time_of_last_construction_;
int32_t enemy_last_seen_;
@@ -232,13 +265,20 @@
Widelands::Coords
last_attack_target_; // flag to abuilding (position) that was attacked last time
int32_t next_attack_waittime_; // second till the next attack consideration
- int32_t spots_; // sum of buildable fields
+ bool seafaring_economy; // false by default, until first port space is found
+ uint32_t colony_scan_area_; // distance from a possible port that is scanned for owned territory
+ // it decreases with failed scans
+ int32_t spots_; // sum of buildable fields
+
+ enum {kReprioritize, kStopShipyard, kStapShipyard};
+ std::vector<int16_t> marineTaskQueue_;
std::unique_ptr<Notifications::Subscriber<Widelands::NoteFieldPossession>>
field_possession_subscriber_;
std::unique_ptr<Notifications::Subscriber<Widelands::NoteImmovable>> immovable_subscriber_;
std::unique_ptr<Notifications::Subscriber<Widelands::NoteProductionSiteOutOfResources>>
outofresource_subscriber_;
+ std::unique_ptr<Notifications::Subscriber<Widelands::NoteShipMessage>> shipnotes_subscriber_;
};
#endif // end of include guard: WL_AI_DEFAULTAI_H
=== modified file 'src/economy/portdock.cc'
--- src/economy/portdock.cc 2014-12-11 21:32:41 +0000
+++ src/economy/portdock.cc 2014-12-20 20:51:12 +0000
@@ -21,6 +21,8 @@
#include <memory>
+#include <boost/format.hpp>
+
#include "base/log.h"
#include "base/macros.h"
#include "economy/fleet.h"
@@ -165,8 +167,13 @@
}
void PortDock::cleanup(EditorGameBase& egbase) {
+
+ Warehouse* wh = nullptr;
+
if (egbase.objects().object_still_available(m_warehouse)) {
- // Transfer all our wares into the warehouse.
+
+ wh = m_warehouse;
+
if (upcast(Game, game, &egbase)) {
for (ShippingItem& shipping_item : m_waiting) {
WareInstance* ware;
@@ -180,6 +187,7 @@
}
}
}
+
m_waiting.clear();
m_warehouse->m_portdock = nullptr;
}
@@ -203,6 +211,20 @@
}
PlayerImmovable::cleanup(egbase);
+
+ if (wh) {
+ if (upcast(Game, game, &egbase)) {
+ if (game->is_loaded()) {
+ Player& player = owner();
+ log("Message: Portdock lost, trying to restore it (player %d)\n",
+ player.player_number());
+ wh->restore_portdock_or_destroy(egbase);
+ return;
+ }
+ }
+ // this is not a (running) game, destroying the port
+ wh->destroy(egbase);
+ }
}
/**
=== modified file 'src/logic/player.cc'
--- src/logic/player.cc 2014-12-16 21:39:00 +0000
+++ src/logic/player.cc 2014-12-20 20:51:12 +0000
@@ -56,6 +56,7 @@
#include "sound/sound_handler.h"
#include "wui/interactive_player.h"
+namespace Widelands {
namespace {
@@ -90,11 +91,43 @@
}
}
-
-
-}
-
-namespace Widelands {
+/**
+* Sort soldiers on priority, most wanted soldiers first.
+* On return, \param soldiers is filled with up to \param max soldiers.
+*/
+struct SoldierPriority {
+ Soldier* soldier;
+ int priority; // (strength + 1) * health - position in building.
+};
+
+void sort_soldiers(std::vector<SoldierPriority>* soldiers,
+ uint32_t max = std::numeric_limits<uint32_t>::max()) {
+ std::vector<SoldierPriority> temp;
+ SoldierPriority soldier;
+ uint32_t left = soldiers->size();
+
+ while (left) {
+ int32_t maxlevel = -1;
+ uint32_t pos = 0;
+ uint32_t maxpos = 0;
+ for (const SoldierPriority& soldier_priority : *soldiers) {
+ const int32_t level = soldier_priority.priority;
+ if (level > maxlevel) {
+ maxlevel = level;
+ maxpos = pos;
+ soldier.soldier = soldier_priority.soldier;
+ soldier.priority = level;
+ }
+ pos++;
+ }
+ temp.push_back(soldier);
+ soldiers->erase(soldiers->begin() + maxpos);
+ left--;
+ }
+ soldiers->insert(soldiers->end(), temp.begin(), temp.begin() + std::min<int>(max, temp.size()));
+}
+
+}
const RGBColor Player::Colors[MAX_PLAYERS] = {
RGBColor(2, 2, 198), // blue
@@ -879,57 +912,73 @@
*/
/**
- * Get a list of soldiers that this player can use to attack the
- * building at the given flag.
- *
- * The default attack should just take the first N soldiers of the
- * returned array.
- */
-// TODO(unknown): Perform a meaningful sort on the soldiers array.
-uint32_t Player::find_attack_soldiers
- (Flag & flag, std::vector<Soldier *> * soldiers, uint32_t nr_wanted)
-{
- uint32_t count = 0;
+* Return the number of soldiers that can attack the building at the
+* given flag, limited to \param nr_wanted.
+*
+* If \param soldiers != NULL it will be filled with soldiers selected
+* on health and strength while trying to select the soldiers from
+* multiple buildings.
+*/
+uint32_t
+Player::find_attack_soldiers(Flag& flag, std::vector<Soldier*>* soldiers, uint32_t nr_wanted) {
+ std::vector<SoldierPriority> attackers_all;
+ uint32_t nr_available = 0;
if (soldiers)
soldiers->clear();
- Map & map = egbase().map();
- std::vector<BaseImmovable *> flags;
+ Map& map = egbase().map();
+ std::vector<BaseImmovable*> flags;
- map.find_reachable_immovables_unique
- (Area<FCoords>(map.get_fcoords(flag.get_position()), 25),
- flags,
- CheckStepDefault(MOVECAPS_WALK),
- FindFlagOf(FindImmovablePlayerMilitarySite(*this)));
+ map.find_reachable_immovables_unique(Area<FCoords>(map.get_fcoords(flag.get_position()), 25),
+ flags,
+ CheckStepDefault(MOVECAPS_WALK),
+ FindFlagOf(FindImmovablePlayerMilitarySite(*this)));
if (flags.empty())
return 0;
- for (BaseImmovable * temp_flag : flags) {
- upcast(Flag, attackerflag, temp_flag);
- upcast(MilitarySite, ms, attackerflag->get_building());
- std::vector<Soldier *> const present = ms->present_soldiers();
- uint32_t const nr_staying = ms->min_soldier_capacity();
- uint32_t const nr_present = present.size();
+ // Count (and if necessary collect) available soldiers.
+ for (const BaseImmovable* base_immovable : flags) {
+ const Flag* attackerflag = static_cast<const Flag*>(base_immovable);
+ const MilitarySite* ms = static_cast<MilitarySite*>(attackerflag->get_building());
+ std::vector<Soldier*> present = ms->present_soldiers();
+ const uint32_t nr_staying = ms->min_soldier_capacity();
+ const uint32_t nr_present = present.size();
if (nr_staying < nr_present) {
- uint32_t const nr_taken =
- std::min(nr_wanted, nr_present - nr_staying);
- if (soldiers)
- soldiers->insert
- (soldiers->end(),
- present.begin(), present.begin() + nr_taken);
- count += nr_taken;
- nr_wanted -= nr_taken;
- if (!nr_wanted)
- break;
+ const uint32_t nr_taken = nr_present - nr_staying;
+ std::vector<SoldierPriority> attackers;
+ if (soldiers) {
+ SoldierPriority temp;
+ for (Soldier* soldier : present) {
+ temp.soldier = soldier;
+ temp.priority =
+ (soldier->get_level(atrTotal) + 1) * soldier->get_current_hitpoints();
+ attackers.push_back(temp);
+ }
+ sort_soldiers(&attackers, nr_taken); // Leave weakest soldier behind.
+ int32_t pos = 0;
+ for (SoldierPriority& soldier_priority : attackers) {
+ soldier_priority.priority += pos--; // Small penalty for position.
+ }
+ attackers_all.insert(attackers_all.end(), attackers.begin(), attackers.end());
+ }
+ nr_available += nr_taken;
+ }
+ }
+ const uint32_t count = std::min(nr_available, nr_wanted);
+
+ if (soldiers) {
+ // Sort soldiers and remove surplus.
+ sort_soldiers(&attackers_all, count);
+ for (const SoldierPriority& soldier_priority : attackers_all) {
+ soldiers->push_back(soldier_priority.soldier);
}
}
return count;
}
-
// TODO(unknown): Clean this mess up. The only action we really have right now is
// to attack, so pretending we have more types is pointless.
void Player::enemyflagaction
=== modified file 'src/logic/ship.cc'
--- src/logic/ship.cc 2014-12-12 22:59:39 +0000
+++ src/logic/ship.cc 2014-12-20 20:51:12 +0000
@@ -107,6 +107,7 @@
void Ship::init(EditorGameBase& egbase) {
Bob::init(egbase);
init_fleet(egbase);
+ Notifications::publish(NoteShipMessage(this, NoteShipMessage::Message::kGained));
}
/**
@@ -132,6 +133,8 @@
m_items.pop_back();
}
+ Notifications::publish(NoteShipMessage(this, NoteShipMessage::Message::kLost));
+
Bob::cleanup(egbase);
}
@@ -162,10 +165,10 @@
* ivar1 = helper flag for coordination of mutual evasion of ships
*/
const Bob::Task Ship::taskShip = {
- "ship",
- static_cast<Bob::Ptr>(&Ship::ship_update),
- nullptr,
- nullptr,
+ "ship",
+ static_cast<Bob::Ptr>(&Ship::ship_update),
+ nullptr,
+ nullptr,
true // unique task
};
@@ -452,6 +455,9 @@
send_message(game, "exp_port_space", msg_head, msg_body, "port.png");
}
m_expedition->seen_port_buildspaces.swap(temp_port_buildspaces);
+ if (new_port_space) {
+ Notifications::publish(NoteShipMessage(this, NoteShipMessage::Message::kWaitingForCommand));
+ }
}
}
@@ -564,6 +570,10 @@
send_message(
game, "exp_island", msg_head, msg_body, "ship_explore_island_cw.png");
m_ship_state = EXP_WAITING;
+
+ Notifications::publish(
+ NoteShipMessage(this, NoteShipMessage::Message::kWaitingForCommand));
+
return start_task_idle(game, descr().main_animation(), 1500);
}
}
@@ -620,6 +630,9 @@
std::string msg_body =
_("An expedition ship reached a coast and is waiting for further commands.");
send_message(game, "exp_coast", msg_head, msg_body, "ship_explore_island_cw.png");
+
+ Notifications::publish(NoteShipMessage(this, NoteShipMessage::Message::kWaitingForCommand));
+
return;
}
}
@@ -627,45 +640,66 @@
assert(m_expedition->seen_port_buildspaces && !m_expedition->seen_port_buildspaces->empty());
BaseImmovable* baim =
game.map()[m_expedition->seen_port_buildspaces->front()].get_immovable();
- assert(baim);
- upcast(ConstructionSite, cs, baim);
+ if (baim) {
+ upcast(ConstructionSite, cs, baim);
- for (int i = m_items.size() - 1; i >= 0; --i) {
- WareInstance* ware;
- Worker* worker;
- m_items.at(i).get(game, &ware, &worker);
- if (ware) {
- // no, we don't transfer the wares, we create new ones out of air and remove the old
- // ones ;)
- WaresQueue& wq = cs->waresqueue(ware->descr_index());
- const uint32_t max = wq.get_max_fill();
- const uint32_t cur = wq.get_filled();
- assert(max > cur);
- wq.set_filled(cur + 1);
- m_items.at(i).remove(game);
- m_items.resize(i);
- break;
- } else {
- assert(worker);
- worker->set_economy(nullptr);
- worker->set_location(cs);
- worker->set_position(game, cs->get_position());
- worker->reset_tasks(game);
- PartiallyFinishedBuilding::request_builder_callback(
- game, *cs->get_builder_request(), worker->descr().worker_index(), worker, *cs);
- m_items.resize(i);
+ for (int i = m_items.size() - 1; i >= 0; --i) {
+ WareInstance* ware;
+ Worker* worker;
+ m_items.at(i).get(game, &ware, &worker);
+ if (ware) {
+ // no, we don't transfer the wares, we create new ones out of air and remove the old
+ // ones ;)
+ WaresQueue& wq = cs->waresqueue(ware->descr_index());
+ const uint32_t max = wq.get_max_fill();
+ const uint32_t cur = wq.get_filled();
+ assert(max > cur);
+ wq.set_filled(cur + 1);
+ m_items.at(i).remove(game);
+ m_items.resize(i);
+ break;
+ } else {
+ assert(worker);
+ worker->set_economy(nullptr);
+ worker->set_location(cs);
+ worker->set_position(game, cs->get_position());
+ worker->reset_tasks(game);
+ PartiallyFinishedBuilding::request_builder_callback(
+ game, *cs->get_builder_request(), worker->descr().worker_index(), worker, *cs);
+ m_items.resize(i);
+ }
}
+ } else { // it seems that port constructionsite has dissapeared
+ // Send a message to the player, that a port constructionsite is gone
+ std::string msg_head = _("New port constructionsite is gone");
+ std::string msg_body = _("Unloading of wares failed, expedition is cancelled now.");
+ send_message(game, "exp_port_space", msg_head, msg_body, "port.png");
+ send_signal(game, "cancel_expedition");
}
- if (m_items.empty()) {
+
+ if (m_items.empty() || !baim) { // we are done, either way
m_ship_state = TRANSPORT; // That's it, expedition finished
+ // Bring us back into a fleet and a economy.
init_fleet(game);
+
+ // for case that there are any workers left on board
+ // (applicable when port construction space is kLost)
+ Worker* worker;
+ for (ShippingItem& item : m_items) {
+ item.get(game, nullptr, &worker);
+ if (worker) {
+ worker->reset_tasks(game);
+ worker->start_task_shipping(game, nullptr);
+ }
+ }
+
m_expedition.reset(nullptr);
if (upcast(InteractiveGameBase, igb, game.get_ibase()))
refresh_window(*igb);
+ return start_task_idle(game, descr().main_animation(), 1500);
}
- return start_task_idle(game, descr().main_animation(), 1500); // unload the next item
}
default: {
@@ -785,6 +819,7 @@
const std::string msg_head = _("Expedition Ready");
const std::string msg_body = _("An expedition ship is waiting for your commands.");
send_message(game, "exp_ready", msg_head, msg_body, "start_expedition.png");
+ Notifications::publish(NoteShipMessage(this, NoteShipMessage::Message::kWaitingForCommand));
}
/// Initializes / changes the direction of scouting to @arg direction
@@ -820,6 +855,7 @@
void Ship::exp_cancel(Game& game) {
// Running colonization has the highest priority before cancelation
// + cancelation only works if an expedition is actually running
+
if ((m_ship_state == EXP_COLONIZING) || !state_is_expedition())
return;
send_signal(game, "cancel_expedition");
@@ -900,11 +936,11 @@
const std::string& picture) {
std::string rt_description;
if (picture.size() > 3) {
- rt_description = (boost::format("<rt image=pics/%s><p font-face=serif font-size=14>")
- % picture).str();
- } else {
- rt_description = "<rt><p font-face=serif font-size=14>";
- }
+ rt_description = "<rt image=pics/";
+ rt_description += picture;
+ rt_description += "><p font-size=14 font-face=DejaVuSerif>";
+ } else
+ rt_description = "<rt><p font-size=14 font-face=DejaVuSerif>";
rt_description += description;
rt_description += "</p></rt>";
=== modified file 'src/logic/ship.h'
--- src/logic/ship.h 2014-09-10 08:55:04 +0000
+++ src/logic/ship.h 2014-12-20 20:51:12 +0000
@@ -37,6 +37,19 @@
struct Fleet;
class PortDock;
+struct NoteShipMessage {
+ CAN_BE_SEND_AS_NOTE(NoteId::ShipMessage)
+
+ Ship* ship;
+
+ enum class Message {kLost, kGained, kWaitingForCommand};
+ Message message;
+
+ NoteShipMessage(Ship* const init_ship, Message const init_message)
+ : ship(init_ship), message(init_message) {
+ }
+};
+
struct ShipDescr : BobDescr {
ShipDescr
(char const * name, char const * descname,
=== modified file 'src/logic/warehouse.cc'
--- src/logic/warehouse.cc 2014-12-12 22:59:39 +0000
+++ src/logic/warehouse.cc 2014-12-20 20:51:12 +0000
@@ -438,7 +438,9 @@
Map& map = egbase.map();
std::vector<Coords> dock = map.find_portdock(get_position());
if (dock.empty()) {
- log("Attempting to setup port without neighboring water.\n");
+ log("Attempting to setup port without neighboring water (coords: %3dx%3d).\n",
+ get_position().x,
+ get_position().y);
return;
}
@@ -460,10 +462,26 @@
Building::destroy(egbase);
}
+// if the port still exists and we are in game we first try to restore the portdock
+void Warehouse::restore_portdock_or_destroy(EditorGameBase& egbase) {
+ // re-init the portdock
+ Warehouse::init_portdock(egbase);
+ if (!m_portdock) {
+ log(" Portdock lost, removing the port now (coords: %3dx%3d)\n",
+ get_position().x,
+ get_position().y);
+ Building::destroy(egbase);
+ }
+}
+
/// Destroy the warehouse.
void Warehouse::cleanup(EditorGameBase& egbase) {
+
+ // storing object of the portdock if exists
+ PortDock* pd = nullptr;
+
if (egbase.objects().object_still_available(m_portdock)) {
- m_portdock->remove(egbase);
+ pd = m_portdock;
m_portdock = nullptr;
}
@@ -497,7 +515,23 @@
Player& player = owner();
player.unsee_area(Area<FCoords>(map.get_fcoords(get_position()), descr().vision_range()));
+ if (upcast(Game, game, &egbase)) {
+ log("Message: removing %s (player %i)\n",
+ to_string(descr().type()).c_str(),
+ player.player_number());
+ send_message(*game,
+ "warehouse",
+ descr().descname(),
+ (boost::format(_("A %s was destroyed.")) % descr().descname().c_str()).str(),
+ true);
+ }
+
Building::cleanup(egbase);
+
+ // if there was a portdock, removing it now
+ if (pd) {
+ pd->remove(egbase);
+ }
}
/// Act regularly to create workers of buildable types without cost. According
=== modified file 'src/logic/warehouse.h'
--- src/logic/warehouse.h 2014-09-19 12:54:54 +0000
+++ src/logic/warehouse.h 2014-12-20 20:51:12 +0000
@@ -136,6 +136,8 @@
void destroy(EditorGameBase &) override;
+ void restore_portdock_or_destroy(EditorGameBase &);
+
void act(Game & game, uint32_t data) override;
void set_economy(Economy *) override;
=== modified file 'src/notifications/note_ids.h'
--- src/notifications/note_ids.h 2014-11-23 14:34:38 +0000
+++ src/notifications/note_ids.h 2014-12-20 20:51:12 +0000
@@ -32,8 +32,9 @@
FieldPossession,
FieldTransformed,
ProductionSiteOutOfResources,
-
+ ShipMessage,
GraphicResolutionChanged,
+
};
#endif // end of include guard: WL_NOTIFICATIONS_NOTE_IDS_H
Follow ups
-
Re: [Merge] lp:~widelands-dev/widelands/find_attack_soldiers into lp:widelands
From: SirVer, 2016-01-27
-
[Merge] lp:~widelands-dev/widelands/find_attack_soldiers into lp:widelands
From: SirVer, 2016-01-27
-
[Merge] lp:~widelands-dev/widelands/find_attack_soldiers into lp:widelands
From: bunnybot, 2016-01-26
-
[Merge] lp:~widelands-dev/widelands/find_attack_soldiers into lp:widelands
From: bunnybot, 2016-01-25
-
[Merge] lp:~widelands-dev/widelands/find_attack_soldiers into lp:widelands
From: bunnybot, 2016-01-25
-
[Merge] lp:~widelands-dev/widelands/find_attack_soldiers into lp:widelands
From: bunnybot, 2016-01-25
-
Re: [Merge] lp:~widelands-dev/widelands/find_attack_soldiers into lp:widelands
From: SirVer, 2016-01-24
-
Bunnybot says...
From: bunnybot, 2016-01-20
-
Bunnybot says...
From: bunnybot, 2016-01-03
-
Bunnybot says...
From: bunnybot, 2016-01-02
-
Bunnybot says...
From: bunnybot, 2016-01-02
-
Re: [Merge] lp:~widelands-dev/widelands/find_attack_soldiers into lp:widelands
From: Martin Schmidt, 2015-09-06
-
Re: [Merge] lp:~widelands-dev/widelands/find_attack_soldiers into lp:widelands
From: GunChleoc, 2015-09-04
-
Re: [Merge] lp:~widelands-dev/widelands/find_attack_soldiers into lp:widelands
From: SirVer, 2015-09-03
-
Re: [Merge] lp:~widelands-dev/widelands/find_attack_soldiers into lp:widelands
From: GunChleoc, 2015-05-14
-
Re: [Merge] lp:~widelands-dev/widelands/find_attack_soldiers into lp:widelands
From: TiborB, 2015-05-13
-
Re: [Merge] lp:~widelands-dev/widelands/find_attack_soldiers into lp:widelands
From: TiborB, 2015-04-30
-
Re: [Merge] lp:~widelands-dev/widelands/find_attack_soldiers into lp:widelands
From: Martin Schmidt, 2015-04-25
-
Re: [Merge] lp:~widelands-dev/widelands/find_attack_soldiers into lp:widelands
From: GunChleoc, 2015-04-25
-
Re: [Merge] lp:~widelands-dev/widelands/find_attack_soldiers into lp:widelands
From: TiborB, 2015-04-22
-
Re: [Merge] lp:~widelands-dev/widelands/find_attack_soldiers into lp:widelands
From: Martin Schmidt, 2015-04-22
-
Re: [Merge] lp:~widelands-dev/widelands/find_attack_soldiers into lp:widelands
From: TiborB, 2015-04-22
-
Re: [Merge] lp:~widelands-dev/widelands/find_attack_soldiers into lp:widelands
From: SirVer, 2015-02-24
-
Re: [Merge] lp:~widelands-dev/widelands/find_attack_soldiers into lp:widelands
From: Martin Schmidt, 2015-02-23
-
Re: [Merge] lp:~widelands-dev/widelands/find_attack_soldiers into lp:widelands
From: SirVer, 2015-02-23
-
Re: [Merge] lp:~widelands-dev/widelands/find_attack_soldiers into lp:widelands
From: Martin Schmidt, 2015-02-18
-
Re: [Merge] lp:~widelands-dev/widelands/find_attack_soldiers into lp:widelands
From: Martin Schmidt, 2015-02-16
-
Re: [Merge] lp:~widelands-dev/widelands/find_attack_soldiers into lp:widelands
From: fk, 2015-02-16
-
Re: [Merge] lp:~widelands-dev/widelands/find_attack_soldiers into lp:widelands
From: fk, 2015-02-16
-
Re: [Merge] lp:~widelands-dev/widelands/find_attack_soldiers into lp:widelands
From: Martin Schmidt, 2015-02-16
-
Re: [Merge] lp:~widelands-dev/widelands/find_attack_soldiers into lp:widelands
From: SirVer, 2015-02-16
-
Re: [Merge] lp:~widelands-dev/widelands/find_attack_soldiers into lp:widelands
From: SirVer, 2014-12-20
-
Re: [Merge] lp:~widelands-dev/widelands/find_attack_soldiers into lp:widelands
From: TiborB, 2014-12-20