← Back to team overview

widelands-dev team mailing list archive

[Merge] lp:~widelands-dev/widelands/ai-enhancements into lp:widelands

 

TiborB has proposed merging lp:~widelands-dev/widelands/ai-enhancements into lp:widelands.

Requested reviews:
  Widelands Developers (widelands-dev)
Related bugs:
  Bug #1395521 in widelands: "[7237][7261] AI is too eager to raze newly conquered buildings"
  https://bugs.launchpad.net/widelands/+bug/1395521
  Bug #1421997 in widelands: "Shipcontrols doesn' t work correctly"
  https://bugs.launchpad.net/widelands/+bug/1421997

For more details, see:
https://code.launchpad.net/~widelands-dev/widelands/ai-enhancements/+merge/250207

This is quick fix after seafaring branch merge. I fixed GUI control of ships. There was suggestion by SirVer to add a regression test for it, but I see that LUA api for control a ship movement is needed, so this would be separate job - making LUA interface to navigate the ship and prepare a test for this functionality.

To fix a bug about dismantling unconnected and occupied military buildings - I have to make deeper changes to the code, but I think now it is better.

Also, I changed the way how AI finds about portspaces - now it is bit cleaner...

There is one significant outstanding feature - to teach AI where ports and shipyards can be placed to be close to a "big water" - now AI is quite stupid about it...
-- 
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/ai-enhancements into lp:widelands.
=== modified file 'src/ai/ai_help_structs.h'
--- src/ai/ai_help_structs.h	2015-02-16 13:10:35 +0000
+++ src/ai/ai_help_structs.h	2015-02-18 20:34:30 +0000
@@ -40,6 +40,8 @@
 class ProductionSite;
 class MilitarySite;
 
+enum class ExtendedBool : uint8_t {kUnset, kTrue, kFalse };
+
 struct CheckStepRoadAI {
 	CheckStepRoadAI(Player* const pl, uint8_t const mc, bool const oe)
 	   : player_(pl), movecaps_(mc), open_end_(oe) {
@@ -129,14 +131,13 @@
 struct FindNodeMineable {
 	bool accept(const Map&, const FCoords& fc) const {
 
-		return (fc.field->nodecaps() & BUILDCAPS_MINE) &&
-			(fc.field->get_resources() == res);
+		return (fc.field->nodecaps() & BUILDCAPS_MINE) && (fc.field->get_resources() == res);
 	}
 
 	Game& game;
 	int32_t res;
 
-	FindNodeMineable(Game& g, int32_t r) : game(g), res(r)  {
+	FindNodeMineable(Game& g, int32_t r) : game(g), res(r) {
 	}
 };
 
@@ -248,6 +249,7 @@
 	bool is_portspace_;
 	bool port_nearby_;  // to increase priority if a port is nearby,
 	// especially for new colonies
+	Widelands::ExtendedBool portspace_nearby_;  // prefer military buildings closer to the portspace
 
 	std::vector<uint8_t> consumers_nearby_;
 	std::vector<uint8_t> producers_nearby_;
@@ -259,6 +261,7 @@
 	     enemy_nearby_(0),
 	     unowned_land_nearby_(0),
 	     near_border_(false),
+	     unowned_mines_pots_nearby_(0),
 	     trees_nearby_(0),
 	     // explanation of starting values
 	     // this is done to save some work for AI (CPU utilization)
@@ -279,8 +282,10 @@
 	     military_in_constr_nearby_(0),
 	     military_presence_(0),
 	     military_stationed_(0),
+	     military_unstationed_(0),
 	     is_portspace_(false),
-	     port_nearby_(false) {
+	     port_nearby_(false),
+	     portspace_nearby_(Widelands::ExtendedBool::kUnset) {
 	}
 };
 
@@ -292,24 +297,27 @@
 	bool preferred_;
 
 	int32_t mines_nearby_;
-	//this is to provide that a mine is not built on the edge of mine area
+	// this is to provide that a mine is not built on the edge of mine area
 	int32_t same_mine_fields_nearby_;
 
 	MineableField(const Widelands::FCoords& fc)
-	   : coords(fc), next_update_due_(0), preferred_(false), mines_nearby_(0),
-	   same_mine_fields_nearby_(0) {
+	   : coords(fc),
+	     next_update_due_(0),
+	     preferred_(false),
+	     mines_nearby_(0),
+	     same_mine_fields_nearby_(0) {
 	}
 };
 
 struct EconomyObserver {
 	Widelands::Economy& economy;
 	std::list<Widelands::Flag const*> flags;
-	int32_t next_connection_try;
-	uint32_t failed_connection_tries;
+	int32_t dismantle_grace_time_;
 
 	EconomyObserver(Widelands::Economy& e) : economy(e) {
-		next_connection_try = 0;
-		failed_connection_tries = 0;
+		// next_connection_try = 0;
+		// failed_connection_tries = 0;
+		dismantle_grace_time_ = std::numeric_limits<int32_t>::max();
 	}
 };
 

=== modified file 'src/ai/defaultai.cc'
--- src/ai/defaultai.cc	2015-02-05 12:11:20 +0000
+++ src/ai/defaultai.cc	2015-02-18 20:34:30 +0000
@@ -67,7 +67,7 @@
 constexpr int kMarineDecisionInterval = 20 * 1000;
 constexpr int kTrainingSitesCheckInterval = 30 * 1000;
 
-//this is intended for map developers, by default should be off
+// this is intended for map developers, by default should be off
 constexpr bool kPrintStats = false;
 
 // Some buildings have to be built close to borders and their
@@ -100,8 +100,8 @@
      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_ship_check_due(30 * 1000),
+     next_marine_decisions_due(30 * 1000),
      next_attack_consideration_due_(300000),
      next_trainingsites_check_due_(15 * 60 * 1000),
      next_bf_check_due_(1000),
@@ -130,34 +130,34 @@
 	// Subscribe to NoteFieldPossession.
 	field_possession_subscriber_ =
 	   Notifications::subscribe<NoteFieldPossession>([this](const NoteFieldPossession& note) {
-			if (note.player != player_) {
+		   	if (note.player != player_) {
 			   return;
 		   }
-			if (note.ownership == NoteFieldPossession::Ownership::GAINED) {
+		   	if (note.ownership == NoteFieldPossession::Ownership::GAINED) {
 			   unusable_fields.push_back(note.fc);
 		   }
 		});
 
 	// Subscribe to NoteImmovables.
 	immovable_subscriber_ =
-		Notifications::subscribe<NoteImmovable>([this](const NoteImmovable& note) {
-			if (player_ == nullptr) {
-				return;
-			}
-			if (note.pi->owner().player_number() != player_->player_number()) {
-				return;
-			}
-			if (note.ownership == NoteImmovable::Ownership::GAINED) {
-				gain_immovable(*note.pi);
-			} else {
-				lose_immovable(*note.pi);
-			}
+	   Notifications::subscribe<NoteImmovable>([this](const NoteImmovable& note) {
+		   	if (player_ == nullptr) {
+			   return;
+		   }
+		   	if (note.pi->owner().player_number() != player_->player_number()) {
+			   return;
+		   }
+		   	if (note.ownership == NoteImmovable::Ownership::GAINED) {
+			   gain_immovable(*note.pi);
+		   } else {
+			   lose_immovable(*note.pi);
+		   }
 		});
 
 	// Subscribe to ProductionSiteOutOfResources.
 	outofresource_subscriber_ = Notifications::subscribe<NoteProductionSiteOutOfResources>(
 	   [this](const NoteProductionSiteOutOfResources& note) {
-			if (note.ps->owner().player_number() != player_->player_number()) {
+		   	if (note.ps->owner().player_number() != player_->player_number()) {
 			   return;
 		   }
 
@@ -169,42 +169,42 @@
 	shipnotes_subscriber_ =
 	   Notifications::subscribe<NoteShipMessage>([this](const NoteShipMessage& note) {
 
-			// in a short time between start and late_initialization the player
-			// can get notes that can not be processed.
-			// It seems that this causes no problem, at least no substantial
-			if (player_ == nullptr) {
-				return;
-			}
-			if (note.ship->get_owner()->player_number() != player_->player_number()) {
-			   return;
-		   }
-
-			switch (note.message) {
-
-			case NoteShipMessage::Message::kGained:
+		   // in a short time between start and late_initialization the player
+		   // can get notes that can not be processed.
+		   // It seems that this causes no problem, at least no substantial
+		   	if (player_ == nullptr) {
+			   return;
+		   }
+		   	if (note.ship->get_owner()->player_number() != player_->player_number()) {
+			   return;
+		   }
+
+		   	switch (note.message) {
+
+		   	case NoteShipMessage::Message::kGained:
 			   gain_ship(*note.ship, NewShip::kBuilt);
 			   break;
 
-			case NoteShipMessage::Message::kLost:
-				for (std::list<ShipObserver>::iterator i = allships.begin(); i != allships.end(); ++i) {
-					if (i->ship == note.ship) {
+		   	case NoteShipMessage::Message::kLost:
+			   for (std::list<ShipObserver>::iterator i = allships.begin(); i != allships.end(); ++i) {
+				   	if (i->ship == note.ship) {
 					   allships.erase(i);
 					   break;
 				   }
 			   }
 			   break;
 
-			case NoteShipMessage::Message::kWaitingForCommand:
+		   	case NoteShipMessage::Message::kWaitingForCommand:
 			   for (std::list<ShipObserver>::iterator i = allships.begin(); i != allships.end(); ++i) {
-					if (i->ship == note.ship) {
+				   	if (i->ship == note.ship) {
 					   i->waiting_for_command_ = true;
 					   break;
 				   }
 			   }
 			   break;
-		   default:
+		   	default:
 				;
-			}
+		   }
 		});
 }
 
@@ -339,7 +339,7 @@
 		review_wares_targets(gametime);
 	}
 
-	//print statistics
+	// print statistics
 	if (kPrintStats && next_statistics_report_ <= gametime) {
 		print_stats();
 		next_statistics_report_ += 60 * 60 * 1000;
@@ -515,17 +515,39 @@
 
 	Map& map = game().map();
 
-	//here we scan entire map for own ships
+	// here we generate list of all ports and their vicinity from entire map
+	for (const Coords& c : map.get_port_spaces()) {
+		MapRegion<Area<FCoords>> mr(map, Area<FCoords>(map.get_fcoords(c), 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));
+
+		//the same for NW neighbour of a field
+		Coords c_nw;
+		map.get_tln(c, &c_nw);
+		MapRegion<Area<FCoords>> mr_nw(map, Area<FCoords>(map.get_fcoords(c_nw), 3));
+		do {
+			const int32_t hash = coords_hash(map.get_fcoords(*(mr_nw.location().field)));
+			if (port_reserved_coords.count(hash) == 0)
+				port_reserved_coords.insert(hash);
+		} while (mr_nw.advance(map));
+	}
+
+	if (!port_reserved_coords.empty()) {
+		seafaring_economy = true;
+	}
+
+	// here we scan entire map for own ships
 	std::set<OPtr<Ship>> found_ships;
 	for (int16_t y = 0; y < map.get_height(); ++y) {
 		for (int16_t x = 0; x < map.get_width(); ++x) {
 			FCoords f = map.get_fcoords(Coords(x, y));
-			//there are too many bobs on the map so we investigate
-			//only bobs on water
+			// there are too many bobs on the map so we investigate
+			// only bobs on water
 			if (f.field->nodecaps() & MOVECAPS_SWIM) {
-				for (Bob * bob = f.field->get_first_bob();
-					bob;
-					bob = bob->get_next_on_field()) {
+				for (Bob* bob = f.field->get_first_bob(); bob; bob = bob->get_next_on_field()) {
 					if (upcast(Ship, ship, bob)) {
 						if (ship->get_owner() == player_ && !found_ships.count(ship)) {
 							found_ships.insert(ship);
@@ -537,7 +559,7 @@
 		}
 	}
 
-	//here we scan entire map for owned unused fields and own buildings
+	// here we scan entire map for owned unused fields and own buildings
 	std::set<OPtr<PlayerImmovable>> found_immovables;
 	for (int16_t y = 0; y < map.get_height(); ++y) {
 		for (int16_t x = 0; x < map.get_width(); ++x) {
@@ -558,6 +580,19 @@
 			}
 		}
 	}
+
+	// blocking space consumers vicinity (when reloading a game)
+	for (const ProductionSiteObserver& ps_obs : productionsites) {
+		if (ps_obs.bo->space_consumer_ && !ps_obs.bo->plants_trees_) {
+			MapRegion<Area<FCoords>> mr(
+			   map, Area<FCoords>(map.get_fcoords(ps_obs.site->get_position()), 4));
+			do {
+				BlockedField blocked2(
+				   map.get_fcoords(*(mr.location().field)), game().get_gametime() + 20 * 60 * 1000);
+				blocked_fields.push_back(blocked2);
+			} while (mr.advance(map));
+		}
+	}
 }
 
 /**
@@ -711,22 +746,25 @@
 		}
 	}
 
-	// if curent port is a portspace we need add near fields to a list
-	// of fields prohibited for buildings
+	// identifying portspace fields
 	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 for near porspaces
+	if (field.portspace_nearby_ == Widelands::ExtendedBool::kUnset) {
+		field.portspace_nearby_ = ExtendedBool::kFalse;
+		MapRegion<Area<FCoords>> mr(map, Area<FCoords>(field.coords, 2));
+		do {
+			if (port_reserved_coords.count(coords_hash(mr.location())) > 0) {
+				field.portspace_nearby_ = ExtendedBool::kTrue;
+				break;
+			}
+		} 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) {
@@ -777,7 +815,8 @@
 		}
 
 		// counting fields with fish
-		if (field.water_nearby_ > 0 && (field.fish_nearby_ = -1 || game().get_gametime() % 10 == 0)) {
+		if (field.water_nearby_ > 0 &&
+		    (field.fish_nearby_ == -1 || game().get_gametime() % 10 == 0)) {
 			map.find_fields(Area<FCoords>(field.coords, 6),
 			                &resource_list,
 			                FindNodeResource(world.get_resource("fish")));
@@ -978,12 +1017,7 @@
 
 	// Reset statistics for all buildings
 	for (uint32_t i = 0; i < buildings_.size(); ++i) {
-		if (buildings_.at(i).cnt_built_ > 0) {
-			buildings_.at(i).current_stats_ = 0;
-		} else {
-			buildings_.at(i).current_stats_ = 0;
-		}
-
+		buildings_.at(i).current_stats_ = 0;
 		buildings_.at(i).unoccupied_ = false;
 	}
 
@@ -1698,6 +1732,15 @@
 				if (resource_necessity_water_needed_)
 					prio += bf->distant_water_ * resource_necessity_water_ / 255;
 
+				//special bonus if a portspace is close
+				if (bf->portspace_nearby_ == ExtendedBool::kTrue) {
+					if (num_ports == 0){
+						prio += 25;
+					} else {
+						prio += 5;
+					}
+				}
+
 				if (bo.desc->get_size() < maxsize) {
 					prio = prio - 5;
 				}  // penalty
@@ -2093,12 +2136,12 @@
 
 	// if this is end flag (or sole building) or just randomly
 	if (flag.nr_of_roads() <= 1 || gametime % 200 == 0) {
-		create_shortcut_road(flag, 13, 20);
+		create_shortcut_road(flag, 13, 20, gametime);
 		inhibit_road_building_ = gametime + 800;
 	}
 	// this is when a flag is full
 	else if (flag.current_wares() > 6 && gametime % 10 == 0) {
-		create_shortcut_road(flag, 9, 0);
+		create_shortcut_road(flag, 9, 0, gametime);
 		inhibit_road_building_ = gametime + 400;
 	}
 
@@ -2109,7 +2152,7 @@
 // and tries to find alternative route from one flag to another.
 // if route exists, it is not too long, and current road is not intensively used
 // the road can be dismantled
-bool DefaultAI::dispensable_road_test(const Road& road) {
+bool DefaultAI::dispensable_road_test(Widelands::Road& road) {
 
 	Flag& roadstartflag = road.get_flag(Road::FlagStart);
 	Flag& roadendflag = road.get_flag(Road::FlagEnd);
@@ -2182,66 +2225,82 @@
 
 // trying to connect the flag to another one, be it from own economy
 // or other economy
-bool DefaultAI::create_shortcut_road(const Flag& flag, uint16_t checkradius, uint16_t minred) {
+bool DefaultAI::create_shortcut_road(const Flag& flag,
+                                     uint16_t checkradius,
+                                     uint16_t minred,
+                                     int32_t gametime) {
 
 	// 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;
-	}
-
-	// explanation for 'eco->flags.size() * eco->flags.size()'
-	// The AI is able to dismantle whole economy without warehouse as soon as single
-	// building not connected anywhere. But so fast dismantling is not deserved (probably)
-	// so the bigger economy the longer it takes to be dismantled
-	if (eco->failed_connection_tries > 3 + eco->flags.size() * eco->flags.size()) {
-
-		Building* bld = flag.get_building();
-
-		if (bld) {
-			// first we block the field for 15 minutes, probably it is not good place to build a
-			// building on
-			BlockedField blocked(
-			   game().map().get_fcoords(bld->get_position()), game().get_gametime() + 15 * 60 * 1000);
-			blocked_fields.push_back(blocked);
-			eco->flags.remove(&flag);
-			game().send_player_bulldoze(*const_cast<Flag*>(&flag));
-		}
-		return true;
+	// if we passed grace time this will be last attempt and if it fails
+	// building is destroyes
+	bool last_attempt_ = false;
+
+	// this should not happen, but if the economy has a warehouse and a dismantle
+	// grace time set, we must 'zero' the dismantle grace time
+	if (!flag.get_economy()->warehouses().empty() &&
+	    eco->dismantle_grace_time_ != std::numeric_limits<int32_t>::max()) {
+		eco->dismantle_grace_time_ = std::numeric_limits<int32_t>::max();
+	}
+
+	// first we deal with situations when this is economy with no warehouses
+	// and this is a flag belonging to a building/constructionsite
+	if (flag.get_economy()->warehouses().empty() && flag.get_building()) {
+
+		// if we are within grace time, it is OK, just go on
+		if (eco->dismantle_grace_time_ > gametime &&
+		    eco->dismantle_grace_time_ != std::numeric_limits<int32_t>::max()) {
+			;
+
+		// if grace time is not set, this is probably first time without a warehouse and we must
+		// set it
+		} else if (eco->dismantle_grace_time_ == std::numeric_limits<int32_t>::max()) {
+
+			// constructionsites
+			if (upcast(ConstructionSite const, constructionsite, flag.get_building())) {
+				BuildingObserver& bo =
+				   get_building_observer(constructionsite->building().name().c_str());
+				// first very special case - a port (in the phase of constructionsite)
+				// this might be a new colonization port
+				if (bo.is_port_) {
+					eco->dismantle_grace_time_ = gametime + 60 * 60 * 1000;  // one hour should be enough
+				} else {  // other constructionsites, usually new (standalone) constructionsites
+					eco->dismantle_grace_time_ =
+					   gametime + 30 * 1000 +  // very shot time is enough
+					   (eco->flags.size() * 30 * 1000);  // + 30 seconds for every flag in economy
+				}
+
+			// buildings
+			} else {
+
+				//occupied military buildings get special treatment
+				//(extended grace time)
+				bool occupied_military_ = false;
+				Building* b = flag.get_building();
+				if (upcast(MilitarySite, militb, b)) {
+					if (militb->stationed_soldiers().size() > 0) {
+						occupied_military_ = true;
+					}
+				}
+
+				if (occupied_military_) {
+					eco->dismantle_grace_time_ =
+					   (gametime + 20 * 60 * 1000) + (eco->flags.size() * 20 * 1000);
+					checkradius += 3;
+
+				} else {  // for other normal buildings
+					eco->dismantle_grace_time_ =
+					   gametime + (5 * 60 * 1000) + (eco->flags.size() * 20 * 1000);
+				}
+			}
+
+			// we have passed grace_time - it is time to dismantle
+		} else {
+			last_attempt_ = true;
+			//we increase a check radius in last attempt
+			checkradius += 2;
+		}
 	}
 
 	Map& map = game().map();
@@ -2255,10 +2314,10 @@
 	std::vector<Coords> reachable;
 
 	// vector reachable now contains all suitable fields
-	map.find_reachable_fields(
+	const uint32_t reachable_fields_count = map.find_reachable_fields(
 	   Area<FCoords>(map.get_fcoords(flag.get_position()), checkradius), &reachable, check, functor);
 
-	if (reachable.empty()) {
+	if (reachable_fields_count == 0) {
 		return false;
 	}
 
@@ -2432,6 +2491,18 @@
 	}
 
 	// if all possible roads skipped
+	if (last_attempt_) {
+		Building* bld = flag.get_building();
+		// first we block the field for 15 minutes, probably it is not good place to build a
+		// building on
+		BlockedField blocked(
+		   game().map().get_fcoords(bld->get_position()), game().get_gametime() + 15 * 60 * 1000);
+		blocked_fields.push_back(blocked);
+		eco->flags.remove(&flag);
+		game().send_player_bulldoze(*const_cast<Flag*>(&flag));
+		return true;
+	}
+
 	return false;
 }
 
@@ -2792,7 +2863,7 @@
 	if (gametime < next_marine_decisions_due) {
 		return false;
 	}
-	next_marine_decisions_due += kMarineDecisionInterval;
+	next_marine_decisions_due = gametime + kMarineDecisionInterval;
 
 	if (!seafaring_economy) {
 		return false;
@@ -2805,7 +2876,6 @@
 	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)
@@ -2816,10 +2886,6 @@
 				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);
 			}
 		}
 	}
@@ -2852,35 +2918,13 @@
 		}
 	}
 
-	// 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_iter = remote_ports_coords.begin();
-	     ports_iter != remote_ports_coords.end();
-	     ++ports_iter) {
-		still_ours = false;
-		FCoords fcoords = game().map().get_fcoords(coords_unhash(*ports_iter));
-		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_iter);
-			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)) {
+	if (static_cast<float>(allships.size()) >= ports_count) {
 		enough_ships = FleetStatus::kEnoughShips;
-	} else if (static_cast<float>(allships.size()) <
-	           static_cast<float>((territories_count - 1) * 0.6 + ports_count * 0.75)) {
+	} else if (static_cast<float>(allships.size()) < ports_count) {
 		enough_ships = FleetStatus::kNeedShip;
 	}
 
@@ -2930,7 +2974,7 @@
 		return false;
 	}
 
-	next_ship_check_due += kShipCheckInterval;
+	next_ship_check_due = gametime + kShipCheckInterval;
 
 	if (!seafaring_economy) {
 		return false;
@@ -3581,8 +3625,8 @@
 		}
 
 		// sometimes we search for any owned teritory (f.e. when considering
-		//a port location), but when testing (starting from) own military building
-		//we must ignore own teritory, of course
+		// a port location), but when testing (starting from) own military building
+		// we must ignore own teritory, of course
 		if (f->get_owned_by() > 0) {
 			if (type == WalkSearch::kAnyPlayer ||
 			    (type == WalkSearch::kOtherPlayers && f->get_owned_by() != pn)) {
@@ -3693,8 +3737,8 @@
 		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));
+			// 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;
@@ -3842,6 +3886,15 @@
 			if (bo.is_port_) {
 				++num_ports;
 				seafaring_economy = true;
+				// unblock nearby fields, might be used for other buildings...
+				Map& map = game().map();
+				MapRegion<Area<FCoords>> mr(
+				   map, Area<FCoords>(map.get_fcoords(warehousesites.back().site->get_position()), 4));
+				do {
+					const int32_t hash = coords_hash(map.get_fcoords(*(mr.location().field)));
+					if (port_reserved_coords.count(hash) > 0)
+						port_reserved_coords.erase(hash);
+				} while (mr.advance(map));
 			}
 		}
 	}
@@ -4257,10 +4310,24 @@
 
 	PlayerNumber const pn = player_number();
 
-	//we test following materials
-	const std::vector <std::string> materials = {"coal", "log", "ironore", "marble",
-		 "plank", "water", "goldore", "granite", "fish", "diamond", "stone", "corn",
-		 "wheat", "grape", "quartz", "bread", "meat" };
+	// we test following materials
+	const std::vector<std::string> materials = {"coal",
+	                                            "log",
+	                                            "ironore",
+	                                            "marble",
+	                                            "plank",
+	                                            "water",
+	                                            "goldore",
+	                                            "granite",
+	                                            "fish",
+	                                            "diamond",
+	                                            "stone",
+	                                            "corn",
+	                                            "wheat",
+	                                            "grape",
+	                                            "quartz",
+	                                            "bread",
+	                                            "meat"};
 	std::string summary = "";
 	for (uint32_t j = 0; j < materials.size(); ++j) {
 		WareIndex const index = tribe_->ware_index(materials.at(j));
@@ -4272,12 +4339,12 @@
 		}
 		summary = summary + materials.at(j) + ", ";
 	}
-	log (" %1d: Buildings: Pr:%3d, Ml:%3d, Mi:%2d, Wh:%2d, Po:%2d. Missing: %s\n",
-		pn,
-		productionsites.size(),
-		militarysites.size(),
-		mines_.size(),
-		warehousesites.size() - num_ports,
-		num_ports,
-		summary.c_str());
+	log(" %1d: Buildings: Pr:%3d, Ml:%3d, Mi:%2d, Wh:%2d, Po:%2d. Missing: %s\n",
+	    pn,
+	    productionsites.size(),
+	    militarysites.size(),
+	    mines_.size(),
+	    warehousesites.size() - num_ports,
+	    num_ports,
+	    summary.c_str());
 }

=== modified file 'src/ai/defaultai.h'
--- src/ai/defaultai.h	2015-02-16 10:22:29 +0000
+++ src/ai/defaultai.h	2015-02-18 20:34:30 +0000
@@ -154,9 +154,12 @@
 	// if needed it calls create_shortcut_road() with a flag from which
 	// new road should be considered (or is needed)
 	bool improve_roads(int32_t);
-	bool create_shortcut_road(const Widelands::Flag&, uint16_t maxcheckradius, uint16_t minred);
+	bool create_shortcut_road(const Widelands::Flag&,
+	                          uint16_t maxcheckradius,
+	                          uint16_t minred,
+	                          const int32_t gametime);
 	// trying to identify roads that might be removed
-	bool dispensable_road_test(const Widelands::Road&);
+	bool dispensable_road_test(Widelands::Road&);
 	bool check_economies();
 	bool check_productionsites(int32_t);
 	bool check_trainingsites(int32_t);
@@ -226,8 +229,6 @@
 	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;

=== modified file 'src/economy/portdock.cc'
--- src/economy/portdock.cc	2015-02-10 21:25:14 +0000
+++ src/economy/portdock.cc	2015-02-18 20:34:30 +0000
@@ -214,11 +214,13 @@
 	PlayerImmovable::cleanup(egbase);
 
 	//now let attempt to recreate the portdock
-	if (!wh->m_cleanup_in_progress){
-		if (upcast(Game, game, &egbase)) {
-			if (game->is_loaded()) { //do not attempt when shutting down
-				Player& player = owner();
-				wh->restore_portdock_or_destroy(egbase);
+	if (wh) {
+		if (!wh->m_cleanup_in_progress){
+			if (upcast(Game, game, &egbase)) {
+				if (game->is_loaded()) { //do not attempt when shutting down
+					Player& player = owner();
+					wh->restore_portdock_or_destroy(egbase);
+				}
 			}
 		}
 	}
@@ -439,14 +441,16 @@
 void PortDock::log_general_info(const EditorGameBase& egbase) {
 	PlayerImmovable::log_general_info(egbase);
 
-	Coords pos(m_warehouse->get_position());
-	molog("PortDock for warehouse %u (at %i,%i) in fleet %u, need_ship: %s, waiting: %" PRIuS "\n",
-	      m_warehouse ? m_warehouse->serial() : 0,
-	      pos.x,
-	      pos.y,
-	      m_fleet ? m_fleet->serial() : 0,
-	      m_need_ship ? "true" : "false",
-	      m_waiting.size());
+	if (!m_warehouse) {
+		Coords pos(m_warehouse->get_position());
+		molog("PortDock for warehouse %u (at %i,%i) in fleet %u, need_ship: %s, waiting: %" PRIuS "\n",
+		      m_warehouse ? m_warehouse->serial() : 0,
+		      pos.x,
+		      pos.y,
+		      m_fleet ? m_fleet->serial() : 0,
+		      m_need_ship ? "true" : "false",
+		      m_waiting.size());
+	}
 
 	for (ShippingItem& shipping_item : m_waiting) {
 		molog("  IT %u, destination %u\n",

=== modified file 'src/logic/playercommand.cc'
--- src/logic/playercommand.cc	2015-02-13 18:48:37 +0000
+++ src/logic/playercommand.cc	2015-02-18 20:34:30 +0000
@@ -785,9 +785,11 @@
 	upcast(Ship, ship, game.objects().get_object(serial));
 	if (ship && ship->get_owner()->player_number() == sender()) {
 		if (!(ship->get_ship_state() == Widelands::Ship::EXP_WAITING ||
-			ship->get_ship_state() == Widelands::Ship::EXP_FOUNDPORTSPACE)) {
+			ship->get_ship_state() == Widelands::Ship::EXP_FOUNDPORTSPACE ||
+			ship->get_ship_state() == Widelands::Ship::EXP_SCOUTING)) {
 			log (" %1d:ship on %3dx%3d received scout command but not in "
-				"EXP_WAITING or PORTSPACE_FOUND status (expedition: %s), ignoring...\n",
+				"EXP_WAITING or PORTSPACE_FOUND or EXP_SCOUTING status "
+				"(expedition: %s), ignoring...\n",
 				ship->get_owner()->player_number(),
 				ship->get_position().x,
 				ship->get_position().y,
@@ -918,9 +920,11 @@
 	upcast(Ship, ship, game.objects().get_object(serial));
 	if (ship && ship->get_owner()->player_number() == sender()) {
 		if (!(ship->get_ship_state() == Widelands::Ship::EXP_WAITING ||
-			ship->get_ship_state() == Widelands::Ship::EXP_FOUNDPORTSPACE)) {
+			ship->get_ship_state() == Widelands::Ship::EXP_FOUNDPORTSPACE ||
+			ship->get_ship_state() == Widelands::Ship::EXP_SCOUTING)) {
 			log (" %1d:ship on %3dx%3d received explore island command "
-			"but not in EXP_WAITING or PORTSPACE_FOUND status (expedition: %s), ignoring...\n",
+			"but not in EXP_WAITING or PORTSPACE_FOUND or EXP_SCOUTING "
+			"status (expedition: %s), ignoring...\n",
 				ship->get_owner()->player_number(),
 				ship->get_position().x,
 				ship->get_position().y,

=== modified file 'src/logic/warehouse.cc'
--- src/logic/warehouse.cc	2015-02-10 21:25:14 +0000
+++ src/logic/warehouse.cc	2015-02-18 20:34:30 +0000
@@ -285,6 +285,7 @@
 		m_next_worker_without_cost_spawn[i] = never();
 	}
 	m_next_stock_remove_act = 0;
+	m_cleanup_in_progress = false;
 }
 
 

=== modified file 'tribes/atlanteans/port/conf'
--- tribes/atlanteans/port/conf	2014-07-28 12:48:04 +0000
+++ tribes/atlanteans/port/conf	2015-02-18 20:34:30 +0000
@@ -26,3 +26,7 @@
 [build]
 pics=port_b_??.png  # 4 frames
 hotspot=74 70
+
+[aihints]
+prohibited_till=900
+

=== modified file 'tribes/atlanteans/shipyard/conf'
--- tribes/atlanteans/shipyard/conf	2014-10-07 20:06:46 +0000
+++ tribes/atlanteans/shipyard/conf	2015-02-18 20:34:30 +0000
@@ -2,6 +2,7 @@
 
 [aihints]
 needs_water=true
+prohibited_till=1500
 
 [buildcost]
 log=3

=== modified file 'tribes/barbarians/port/conf'
--- tribes/barbarians/port/conf	2014-07-28 12:48:04 +0000
+++ tribes/barbarians/port/conf	2015-02-18 20:34:30 +0000
@@ -28,3 +28,7 @@
 [build]
 pics=port_b_??.png  # 4 frames
 hotspot=67 80
+
+[aihints]
+prohibited_till=900
+

=== modified file 'tribes/barbarians/shipyard/conf'
--- tribes/barbarians/shipyard/conf	2014-10-07 20:06:46 +0000
+++ tribes/barbarians/shipyard/conf	2015-02-18 20:34:30 +0000
@@ -2,6 +2,7 @@
 
 [aihints]
 needs_water=true
+prohibited_till=1500
 
 [buildcost]
 log=3

=== modified file 'tribes/empire/port/conf'
--- tribes/empire/port/conf	2014-07-28 12:48:04 +0000
+++ tribes/empire/port/conf	2015-02-18 20:34:30 +0000
@@ -27,3 +27,7 @@
 [build]
 pics=port_b_??.png  # 4 frames
 hotspot=74 96
+
+[aihints]
+prohibited_till=900
+

=== modified file 'tribes/empire/shipyard/conf'
--- tribes/empire/shipyard/conf	2014-12-27 21:51:34 +0000
+++ tribes/empire/shipyard/conf	2015-02-18 20:34:30 +0000
@@ -2,6 +2,7 @@
 
 [aihints]
 needs_water=true
+prohibited_till=1500
 
 [buildcost]
 log=3


Follow ups