← Back to team overview

widelands-dev team mailing list archive

[Merge] lp:~widelands-dev/widelands/ai_differentiation into lp:widelands

 

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

Requested reviews:
  Widelands Developers (widelands-dev)

For more details, see:
https://code.launchpad.net/~widelands-dev/widelands/ai_differentiation/+merge/278517

This is bunch of changes to allow limitting the number of buildings per difficulty level, it introduces two new flags for init.lua files weak_ai_limit and normal_ai_limit to limit number of buildings. And related changes to C++ code.

Also I fixed couple of small issues, related to upgrade of buildings and so on, the ones I run into during testing.

I populated init.lua files somehow but they would need more attention. Anyway, a rough testing shows promising results...

What is open is renaming of levels (we discussed about this on forum). So I have not renamed them yet.


-- 
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/ai_differentiation into lp:widelands.
=== modified file 'src/ai/ai_help_structs.h'
--- src/ai/ai_help_structs.h	2015-11-21 18:25:56 +0000
+++ src/ai/ai_help_structs.h	2015-11-24 21:27:31 +0000
@@ -43,7 +43,7 @@
 
 enum class ExtendedBool : uint8_t {kUnset, kTrue, kFalse};
 enum class BuildingNecessity : uint8_t
-	{kForced, kNeeded, kNotNeeded, kUnset, kNotBuildable, kAllowed, kNeededPending};
+	{kForced, kNeeded, kNotNeeded, kUnset, kNotBuildable, kAllowed, kNeededPending, kForbidden};
 enum class SchedulerTaskId : uint8_t {
 		kBbuildableFieldsCheck,
 		kMineableFieldsCheck,
@@ -443,12 +443,13 @@
 	int32_t cnt_built_;
 	int32_t cnt_under_construction_;
 	int32_t cnt_target_;  // number of buildings as target
+	int32_t cnt_limit_by_aimode_; // limit imposed by weak or normal AI mode
 
 	// used to track amount of wares produced by building
 	uint32_t stocklevel_;
-	int32_t stocklevel_time;  // time when stocklevel_ was last time recalculated
-	int32_t last_dismantle_time_;
-	int32_t construction_decision_time_;
+	uint32_t stocklevel_time;  // time when stocklevel_ was last time recalculated
+	uint32_t last_dismantle_time_;
+	uint32_t construction_decision_time_;
 
 	uint32_t unoccupied_count_;
 

=== modified file 'src/ai/ai_hints.cc'
--- src/ai/ai_hints.cc	2015-10-25 08:06:00 +0000
+++ src/ai/ai_hints.cc	2015-11-24 21:27:31 +0000
@@ -40,6 +40,8 @@
 	  // 10 days default
 	  forced_after_(table->has_key("forced_after") ? table->get_int("forced_after") : 864000),
 	  mines_percent_(table->has_key("mines_percent") ? table->get_int("mines_percent") : 100),
+      weak_ai_limit_(table->has_key("weak_ai_limit") ? table->get_int("weak_ai_limit") : -1),
+      normal_ai_limit_(table->has_key("normal_ai_limit") ? table->get_int("normal_ai_limit") : -1),
 	  trainingsite_type_(TrainingSiteType::kNoTS) {
 
 	if (table->has_key("trainingsite_type")) {

=== modified file 'src/ai/ai_hints.h'
--- src/ai/ai_hints.h	2015-11-02 08:01:37 +0000
+++ src/ai/ai_hints.h	2015-11-24 21:27:31 +0000
@@ -102,6 +102,14 @@
 		return mines_percent_;
 	}
 
+	int16_t get_weak_ai_limit() const {
+		return weak_ai_limit_;
+	}
+
+	int16_t get_normal_ai_limit() const {
+		return normal_ai_limit_;
+	}
+
 	TrainingSiteType get_trainingsite_type() const {
 		return trainingsite_type_;
 	}
@@ -121,7 +129,9 @@
 	bool shipyard_;
 	int32_t prohibited_till_;
 	int32_t forced_after_;
-	uint8_t mines_percent_;
+	int8_t mines_percent_;
+	int16_t weak_ai_limit_;
+	int16_t normal_ai_limit_;
 	TrainingSiteType trainingsite_type_;
 
 	DISALLOW_COPY_AND_ASSIGN(BuildingHints);

=== modified file 'src/ai/defaultai.cc'
--- src/ai/defaultai.cc	2015-11-22 19:26:11 +0000
+++ src/ai/defaultai.cc	2015-11-24 21:27:31 +0000
@@ -512,6 +512,7 @@
 		bo.cnt_built_ = 0;
 		bo.cnt_under_construction_ = 0;
 		bo.cnt_target_ = 1;  // default for everything
+		bo.cnt_limit_by_aimode_ = std::numeric_limits<int32_t>::max();
 		bo.stocklevel_ = 0;
 		bo.stocklevel_time = 0;
 		bo.last_dismantle_time_ = 0;
@@ -556,6 +557,22 @@
 			bo.plants_trees_ = false;
 		}
 
+	// Is total count of this building limited by AI mode?
+	if (type_ == DEFENSIVE && bh.get_weak_ai_limit() >= 0) {
+		bo.cnt_limit_by_aimode_ = bh.get_weak_ai_limit();
+		log (" %d: AI defensive mode: applying limit %d building(s) for %s\n",
+		player_number(),
+		bo.cnt_limit_by_aimode_,
+		bo.name);
+	}
+	if (type_ == NORMAL && bh.get_normal_ai_limit() >= 0) {
+		bo.cnt_limit_by_aimode_ = bh.get_normal_ai_limit();
+		log (" %d: AI normal mode: applying limit %d building(s) for %s\n",
+		player_number(),
+		bo.cnt_limit_by_aimode_,
+		bo.name);
+	}
+
 		// Read all interesting data from ware producing buildings
 		if (bld.type() == MapObjectType::PRODUCTIONSITE) {
 			const ProductionSiteDescr& prod = dynamic_cast<const ProductionSiteDescr&>(bld);
@@ -1686,11 +1703,21 @@
 					throw wexception("AI: Max presciousness must not be <= 0 for building: %s",
 										  bo.desc->name().c_str());
 				}
-			} else {
-				// For other situations we set max_needed_preciousness_ to zero
+			} else if (bo.new_building_ == BuildingNecessity::kForbidden) {
 				bo.max_needed_preciousness_ = 0;
+			} else {
+				// For other situations we make sure max_needed_preciousness_ is zero
+				assert (bo.max_needed_preciousness_ == 0);
 			}
 
+			// Positive max_needed_preciousness_ says a building type is needed
+			// here we increase or reset the counter
+			// The counter is added to score when considering new building
+			if (bo.max_needed_preciousness_ > 0) {
+				bo.new_building_overdue_ += 1;
+			} else {
+				bo.new_building_overdue_ = 0;
+			}
 
 			// Here we consider a time how long a building needed
 			// We calculate primary_priority used later in construct_building(),
@@ -1734,7 +1761,13 @@
 
 		} else if (bo.type == BuildingObserver::MILITARYSITE) {
 			bo.new_building_ = check_building_necessity(bo.desc->get_size(), gametime);
-		} else if  (bo.type == BuildingObserver::TRAININGSITE && bo.build_material_shortage_) {
+		} else if  (bo.type == BuildingObserver::TRAININGSITE
+			&& // if we dont have enough build material or are above target of current ai mode
+			(bo.build_material_shortage_
+			||
+			bo.total_count() - bo.unconnected_count_ >= bo.cnt_limit_by_aimode_)) {
+					bo.new_building_ = BuildingNecessity::kNotNeeded;
+		} else if (bo.total_count() - bo.unconnected_count_ >= bo.cnt_limit_by_aimode_) {
 			bo.new_building_ = BuildingNecessity::kNotNeeded;
 		} else {
 			bo.new_building_ = BuildingNecessity::kAllowed;
@@ -1778,7 +1811,8 @@
 			}
 
 			if (bo.new_building_ == BuildingNecessity::kNotNeeded ||
-				bo.new_building_ == BuildingNecessity::kNeededPending) {
+				bo.new_building_ == BuildingNecessity::kNeededPending ||
+				bo.new_building_ == BuildingNecessity::kForbidden) {
 				continue;
 				}
 
@@ -3096,12 +3130,14 @@
 
 	const DescriptionIndex enhancement = site.site->descr().enhancement();
 	if (connected_to_wh && enhancement != INVALID_INDEX &&
-		(site.bo->cnt_built_ - site.bo->unoccupied_count_ > 1 ||
-		((site.bo->upgrade_substitutes_ || site.bo->upgrade_extends_) &&
+		// if upgrade does not subsitute, we need to have two buildings at least
+		((site.bo->cnt_built_ - site.bo->unoccupied_count_ > 1 && site.bo->upgrade_extends_)
+		||
+		site.bo->upgrade_substitutes_) &&
 	    gametime > 45 * 60 * 1000 &&
-	    gametime > site.built_time_ + 20 * 60 * 1000))) {
+	    gametime > site.built_time_ + 20 * 60 * 1000) {
 
-		DescriptionIndex enbld = INVALID_INDEX;  // to get rid of this
+		//DescriptionIndex enbld = INVALID_INDEX;  // to get rid of this
 
 		// Only enhance buildings that are allowed (scenario mode)
 		// do not do decisions too fast
@@ -3109,9 +3145,9 @@
 
 			const BuildingDescr& bld = *tribe_->get_building_descr(enhancement);
 			BuildingObserver& en_bo = get_building_observer(bld.name().c_str());
-			BuildingObserver* bestbld = nullptr;
+			bool doing_upgrade = false;
 
-			if (gametime - en_bo.construction_decision_time_ >= kBuildingMinInterval &&
+			if (gametime - en_bo.construction_decision_time_ >= 10 * 60 * 1000 &&
 			    (en_bo.cnt_under_construction_ + en_bo.unoccupied_count_) == 0) {
 
 				// don't upgrade without workers
@@ -3119,19 +3155,15 @@
 
 					// forcing first upgrade
 					if (en_bo.total_count() == 0) {
-						enbld = enhancement;
-						bestbld = &en_bo;
+						doing_upgrade = true;
 					}
 
 					// if the decision was not made yet, consider normal upgrade
-					if (enbld == INVALID_INDEX) {
+					if (!doing_upgrade) {
 						// compare the performance %
 						if (static_cast<int32_t>(en_bo.current_stats_) -
-						       static_cast<int32_t>(site.bo->current_stats_) >
-						    20) {
-
-							enbld = enhancement;
-							bestbld = &en_bo;
+						    static_cast<int32_t>(site.bo->current_stats_) > 20) {
+								doing_upgrade = true;
 						}
 
 						if ((static_cast<int32_t>(en_bo.current_stats_) > 85 &&
@@ -3139,8 +3171,12 @@
 						    (static_cast<int32_t>(en_bo.current_stats_) > 50 &&
 						     en_bo.total_count() * 4 < site.bo->total_count())) {
 
-							enbld = enhancement;
-							bestbld = &en_bo;
+								doing_upgrade = true;
+						}
+
+						// Dont forget about limitation of number of buildings
+						if (en_bo.cnt_limit_by_aimode_ <= en_bo.total_count() - en_bo.unconnected_count_) {
+							doing_upgrade = false;
 						}
 					}
 				}
@@ -3148,10 +3184,9 @@
 
 			// Enhance if enhanced building is useful
 			// additional: we dont want to lose the old building
-			if (enbld != INVALID_INDEX) {
-				game().send_player_enhance_building(*site.site, enbld);
-				bestbld->construction_decision_time_ = gametime;
-
+			if (doing_upgrade) {
+				game().send_player_enhance_building(*site.site, enhancement);
+				en_bo.construction_decision_time_ = gametime;
 				return true;
 			}
 		}
@@ -3716,19 +3751,23 @@
 	const BuildingDescr& bld = *tribe_->get_building_descr(enhancement);
 	BuildingObserver& en_bo = get_building_observer(bld.name().c_str());
 
-	// if it is too soon for enhancement
-	if (gametime - en_bo.construction_decision_time_ >= kBuildingMinInterval) {
-		// now verify that there are enough workers
-		if (site.site->has_workers(enhancement, game())) {  // enhancing
-			game().send_player_enhance_building(*site.site, enhancement);
-			if (site.bo->max_needed_preciousness_ == 0) {
-				assert (mines_per_type[site.bo->mines_].total_count() <= minimal_mines_count);
-			}
-			if (mines_per_type[site.bo->mines_].total_count() > minimal_mines_count) {
-				assert(site.bo->max_needed_preciousness_ > 0);
-			}
-			en_bo.construction_decision_time_ = gametime;
-			changed = true;
+	// Make sure we do not exceed limit given by AI mode
+	if (en_bo.cnt_limit_by_aimode_ >= en_bo.total_count() - en_bo.unconnected_count_) {
+
+		// if it is too soon for enhancement
+		if (gametime - en_bo.construction_decision_time_ >= kBuildingMinInterval) {
+			// now verify that there are enough workers
+			if (site.site->has_workers(enhancement, game())) {  // enhancing
+				game().send_player_enhance_building(*site.site, enhancement);
+				if (site.bo->max_needed_preciousness_ == 0) {
+					assert (mines_per_type[site.bo->mines_].total_count() <= minimal_mines_count);
+				}
+				if (mines_per_type[site.bo->mines_].total_count() > minimal_mines_count) {
+					assert(site.bo->max_needed_preciousness_ > 0);
+				}
+				en_bo.construction_decision_time_ = gametime;
+				changed = true;
+			}
 		}
 	}
 
@@ -3760,6 +3799,13 @@
 										const PerfEvaluation purpose,
 										const uint32_t gametime) {
 
+	// Very first we finds if AI is allowed to build such building due to its mode
+	if (purpose == PerfEvaluation::kForConstruction
+		&&
+		bo.total_count() - bo.unconnected_count_ >= bo.cnt_limit_by_aimode_) {
+			return BuildingNecessity::kForbidden;
+		}
+
 	// First we iterate over outputs of building, count warehoused stock
 	// and deciding if we have enough on stock (in warehouses)
 	bo.max_preciousness_ = 0;
@@ -3792,15 +3838,6 @@
 		assert (bo.max_preciousness_ > 0);
 	}
 
-	// positive max_needed_preciousness_ says a building type is needed
-	// here we increase of reset the counter
-	// the counter is added to score when considering new building
-	if (bo.max_needed_preciousness_ > 0) {
-		bo.new_building_overdue_ += 1;
-	} else {
-		bo.new_building_overdue_ = 0;
-	}
-
 	// This flag is to be used when buildig is forced. AI will not build another building when
 	// a substitution exists. F.e. mines or pairs like tavern-inn
 	// To skip unnecessary calculation, we calculate this only if we have 0 count of the buildings
@@ -3819,6 +3856,28 @@
 		}
 	}
 
+	// Some buildings are upgraded to ones that does not produce current output, so we need to have
+	// two of current buildings to have at least one left after one of them is upgraded
+	// Logic is: after 30th minute we need second building if there is no enhanced building yet,
+	// and after 90th minute we want second building unconditionally
+	bool needs_second_for_upgrade = false;
+	if (gametime > 30 * 60 * 1000 &&
+		bo.cnt_built_ == 1 &&
+		bo.cnt_under_construction_ == 0 &&
+		bo.upgrade_extends_ &&
+		!bo.upgrade_substitutes_ &&
+		bo.type == BuildingObserver::PRODUCTIONSITE) {
+			const DescriptionIndex enhancement = bo.desc->enhancement();
+			BuildingObserver& en_bo
+				= get_building_observer(tribe_->get_building_descr(enhancement)->name().c_str());
+			if ((gametime > 30 * 60 * 1000 && en_bo.total_count() == 0) ||
+			 	gametime > 90 * 60 * 1000) {
+					// We fake this
+					bo.max_needed_preciousness_ = bo.max_preciousness_;
+					needs_second_for_upgrade = true;
+			}
+	}
+
 	// This function is going to say if a building is needed. But there is a 'new_buildings_stop_'
 	// flag that should be obeyed, but sometimes can be ignored.
 	// So we can have two types of needed: kNeeded and KNeededPending
@@ -3865,25 +3924,28 @@
 			bo.max_needed_preciousness_ = bo.max_preciousness_;
 			return BuildingNecessity::kForced;
 		} else if (bo.prohibited_till_ > gametime) {
-			return BuildingNecessity::kNotNeeded;
+			return BuildingNecessity::kForbidden;
 		} else if (bo.is_hunter_ || bo.is_fisher_) {
 
 			if (bo.max_needed_preciousness_ == 0) {
 				return BuildingNecessity::kNotNeeded;
 			} else if (bo.cnt_under_construction_ + bo.unoccupied_count_ > 0) {
-				return BuildingNecessity::kNotNeeded;
+				return BuildingNecessity::kForbidden;
 			} else if (bo.total_count() > 0 && new_buildings_stop_) {
-				return BuildingNecessity::kNotNeeded;
+				return BuildingNecessity::kForbidden;
 			} else {
 				return BuildingNecessity::kNeeded;
 			}
 		} else if (bo.need_trees_) {
 			if (bo.total_count() > 1 && (bo.cnt_under_construction_ + bo.unoccupied_count_ > 0)) {
-				return BuildingNecessity::kNotNeeded;
+				return BuildingNecessity::kForbidden;
 			}
 			bo.cnt_target_ =
 					   3 + static_cast<int32_t>(mines_.size() + productionsites.size()) / 20;
 
+			// adjusting/decreasing based on cnt_limit_by_aimode_
+			bo.cnt_target_ = limit_cnt_target(bo.cnt_target_, bo.cnt_limit_by_aimode_);
+
 			// for case the wood is not needed yet, to avoid inconsistency later on
 			bo.max_needed_preciousness_ = bo.max_preciousness_;
 
@@ -3897,17 +3959,21 @@
 			bo.cnt_target_ =
 				   2 +
 				   static_cast<int32_t>(mines_.size() + productionsites.size()) / 40;
+
+			// adjusting/decreasing based on cnt_limit_by_aimode_
+			bo.cnt_target_ = limit_cnt_target(bo.cnt_target_, bo.cnt_limit_by_aimode_);
+
 			if (wood_policy_ != WoodPolicy::kAllowRangers) {
-				return BuildingNecessity::kNotNeeded;
+				return BuildingNecessity::kForbidden;
 			}
 			// 150 corresponds to 15 trees
 			if (trees_around_cutters_ < 150) {
 				bo.cnt_target_ *= 4;
 			}
 			if (bo.total_count() > 1 && (bo.cnt_under_construction_ + bo.unoccupied_count_ > 0)) {
-				return BuildingNecessity::kNotNeeded;
+				return BuildingNecessity::kForbidden;
 			} else if (bo.total_count() > bo.cnt_target_) {
-				return BuildingNecessity::kNotNeeded;
+				return BuildingNecessity::kForbidden;
 			}
 			return BuildingNecessity::kNeeded;
 		} else if (bo.need_rocks_ && bo.cnt_under_construction_ + bo.unoccupied_count_ == 0) {
@@ -3916,11 +3982,11 @@
 		} else if (bo.production_hint_ >= 0 && bo.cnt_under_construction_ + bo.unoccupied_count_ == 0) {
 			return BuildingNecessity::kAllowed;
 		} else if (bo.cnt_under_construction_ + bo.unoccupied_count_ > 0 && bo.max_needed_preciousness_ < 10) {
-			return BuildingNecessity::kNotNeeded;
+			return BuildingNecessity::kForbidden;
 		} else if (bo.cnt_under_construction_ + bo.unoccupied_count_ > 0 && gametime < 30 * 60 * 1000) {
-			return BuildingNecessity::kNotNeeded;
+			return BuildingNecessity::kForbidden;
 		} else if (bo.cnt_under_construction_ + bo.unoccupied_count_ > 1) {
-			return BuildingNecessity::kNotNeeded; // for preciousness>=10 and after 30 min
+			return BuildingNecessity::kForbidden; // for preciousness>=10 and after 30 min
 		} else if (bo.type == BuildingObserver::MINE) {
 			if ((mines_per_type[bo.mines_].in_construction + mines_per_type[bo.mines_].finished) == 0) {
 				// unless a mine is prohibited, we want to have at least one of the kind
@@ -3935,8 +4001,8 @@
 			if (bo.max_needed_preciousness_ == 0) {
 				return BuildingNecessity::kNotNeeded;
 			}
-			if (bo.total_count() - bo.unconnected_count_ >= 1 || bo.current_stats_ < 20) {
-				return BuildingNecessity::kNotNeeded;
+			if (bo.current_stats_ < 40) {
+				return BuildingNecessity::kForbidden;
 			}
 			return needed_type;
 		} if (bo.max_needed_preciousness_ > 0) {
@@ -3955,15 +4021,17 @@
 				return needed_type;
 			} else if (bo.current_stats_ > 10 + 70 / bo.outputs_.size()) {
 				return needed_type;
+			} else if (needs_second_for_upgrade) {
+				return needed_type;
 			} else {
-				return BuildingNecessity::kNotNeeded;
+				return BuildingNecessity::kForbidden;
 			}
 		} else if (bo.is_shipyard_) {
 			return BuildingNecessity::kAllowed;
 		} else  if (bo.max_needed_preciousness_ == 0) {
 			return BuildingNecessity::kNotNeeded;
 		} else {
-			return BuildingNecessity::kNotNeeded;
+			return BuildingNecessity::kForbidden;
 		}
 	} else if (purpose == PerfEvaluation::kForDismantle) { // now for dismantling
 		// never dismantle last building (a care should be taken elsewhere)
@@ -4115,6 +4183,8 @@
 	TrainingSite* ts = trainingsites.front().site;
 	TrainingSiteObserver& tso = trainingsites.front();
 
+	assert(tso.bo->total_count() <= tso.bo->cnt_limit_by_aimode_);
+
 	const DescriptionIndex enhancement = ts->descr().enhancement();
 
 	if (enhancement != INVALID_INDEX && ts_without_trainers_ == 0 && mines_.size() > 3 &&
@@ -5648,3 +5718,22 @@
 		value);
 	}
 }
+
+int32_t DefaultAI::limit_cnt_target(const int32_t current_cnt_target, const int32_t ai_limit) {
+
+	if (ai_limit >= std::numeric_limits<int32_t>::max() - 1) {
+		// = ai limit is not set
+		return current_cnt_target;
+	}
+
+	int32_t new_target = current_cnt_target;
+
+	if (current_cnt_target > (ai_limit + 1) / 2) {
+		new_target = (ai_limit + 1) / 2;
+	}
+	assert (new_target * 2 >= ai_limit);
+	assert (new_target > 0);
+	assert (new_target <= ai_limit);
+
+	return new_target;
+}

=== modified file 'src/ai/defaultai.h'
--- src/ai/defaultai.h	2015-11-21 18:25:56 +0000
+++ src/ai/defaultai.h	2015-11-24 21:27:31 +0000
@@ -289,6 +289,8 @@
 	uint32_t msites_in_constr() const;
 	uint32_t msites_built() const;
 
+	int32_t limit_cnt_target(int32_t, int32_t);
+
 	std::vector<WareObserver> wares;
 
 	uint32_t next_ai_think_;

=== modified file 'tribes/buildings/productionsites/atlanteans/sawmill/init.lua'
--- tribes/buildings/productionsites/atlanteans/sawmill/init.lua	2015-10-31 12:11:44 +0000
+++ tribes/buildings/productionsites/atlanteans/sawmill/init.lua	2015-11-24 21:27:31 +0000
@@ -34,7 +34,9 @@
 
    aihints = {
       forced_after = 250,
-      prohibited_till = 250
+      prohibited_till = 250,
+      weak_ai_limit = 1,
+      normal_ai_limit = 2
    },
 
    working_positions = {

=== modified file 'tribes/buildings/productionsites/atlanteans/smelting_works/init.lua'
--- tribes/buildings/productionsites/atlanteans/smelting_works/init.lua	2015-10-31 12:11:44 +0000
+++ tribes/buildings/productionsites/atlanteans/smelting_works/init.lua	2015-11-24 21:27:31 +0000
@@ -33,7 +33,9 @@
    },
 
    aihints = {
-      prohibited_till = 600
+      prohibited_till = 600,
+      weak_ai_limit = 1,
+      normal_ai_limit = 2
    },
 
    working_positions = {

=== modified file 'tribes/buildings/productionsites/atlanteans/smokery/init.lua'
--- tribes/buildings/productionsites/atlanteans/smokery/init.lua	2015-10-31 12:11:44 +0000
+++ tribes/buildings/productionsites/atlanteans/smokery/init.lua	2015-11-24 21:27:31 +0000
@@ -35,7 +35,9 @@
 
    aihints = {
       forced_after = 800,
-      prohibited_till = 180
+      prohibited_till = 180,
+      weak_ai_limit = 1,
+      normal_ai_limit = 2
    },
 
    working_positions = {

=== modified file 'tribes/buildings/productionsites/atlanteans/weaving_mill/init.lua'
--- tribes/buildings/productionsites/atlanteans/weaving_mill/init.lua	2015-10-31 12:11:44 +0000
+++ tribes/buildings/productionsites/atlanteans/weaving_mill/init.lua	2015-11-24 21:27:31 +0000
@@ -35,7 +35,9 @@
 
    aihints = {
       forced_after = 600,
-      prohibited_till = 450
+      prohibited_till = 450,
+      weak_ai_limit = 1,
+      normal_ai_limit = 2
    },
 
    working_positions = {

=== modified file 'tribes/buildings/productionsites/barbarians/lime_kiln/init.lua'
--- tribes/buildings/productionsites/barbarians/lime_kiln/init.lua	2015-10-31 12:11:44 +0000
+++ tribes/buildings/productionsites/barbarians/lime_kiln/init.lua	2015-11-24 21:27:31 +0000
@@ -33,7 +33,9 @@
    },
 
    aihints = {
-      forced_after = 600
+      forced_after = 600,
+      weak_ai_limit = 1,
+      normal_ai_limit = 2
    },
 
    working_positions = {

=== modified file 'tribes/buildings/productionsites/barbarians/smelting_works/init.lua'
--- tribes/buildings/productionsites/barbarians/smelting_works/init.lua	2015-10-31 12:11:44 +0000
+++ tribes/buildings/productionsites/barbarians/smelting_works/init.lua	2015-11-24 21:27:31 +0000
@@ -36,7 +36,9 @@
    },
 
    aihints = {
-      prohibited_till = 400
+      prohibited_till = 400,
+      weak_ai_limit = 1,
+      normal_ai_limit = 2
    },
 
    working_positions = {

=== modified file 'tribes/buildings/productionsites/barbarians/wood_hardener/init.lua'
--- tribes/buildings/productionsites/barbarians/wood_hardener/init.lua	2015-10-31 12:11:44 +0000
+++ tribes/buildings/productionsites/barbarians/wood_hardener/init.lua	2015-11-24 21:27:31 +0000
@@ -43,7 +43,9 @@
 
    aihints = {
       forced_after = 250,
-      prohibited_till = 250
+      prohibited_till = 250,
+      weak_ai_limit = 1,
+      normal_ai_limit = 2
    },
 
    working_positions = {

=== modified file 'tribes/buildings/productionsites/empire/brewery/init.lua'
--- tribes/buildings/productionsites/empire/brewery/init.lua	2015-10-31 12:11:44 +0000
+++ tribes/buildings/productionsites/empire/brewery/init.lua	2015-11-24 21:27:31 +0000
@@ -34,7 +34,9 @@
 
    aihints = {
       forced_after = 900,
-      prohibited_till = 600
+      prohibited_till = 600,
+      weak_ai_limit = 1,
+      normal_ai_limit = 2
    },
 
    working_positions = {

=== modified file 'tribes/buildings/productionsites/empire/sawmill/init.lua'
--- tribes/buildings/productionsites/empire/sawmill/init.lua	2015-10-31 12:11:44 +0000
+++ tribes/buildings/productionsites/empire/sawmill/init.lua	2015-11-24 21:27:31 +0000
@@ -34,7 +34,9 @@
 
    aihints = {
       forced_after = 250,
-      prohibited_till = 250
+      prohibited_till = 250,
+      weak_ai_limit = 1,
+      normal_ai_limit = 2
    },
 
    working_positions = {

=== modified file 'tribes/buildings/productionsites/empire/smelting_works/init.lua'
--- tribes/buildings/productionsites/empire/smelting_works/init.lua	2015-10-31 12:11:44 +0000
+++ tribes/buildings/productionsites/empire/smelting_works/init.lua	2015-11-24 21:27:31 +0000
@@ -39,7 +39,9 @@
    },
 
    aihints = {
-      prohibited_till = 600
+      prohibited_till = 600,
+      weak_ai_limit = 1,
+      normal_ai_limit = 2
    },
 
    working_positions = {

=== modified file 'tribes/buildings/productionsites/empire/stonemasons_house/init.lua'
--- tribes/buildings/productionsites/empire/stonemasons_house/init.lua	2015-10-31 12:11:44 +0000
+++ tribes/buildings/productionsites/empire/stonemasons_house/init.lua	2015-11-24 21:27:31 +0000
@@ -35,7 +35,9 @@
 
    aihints = {
       forced_after = 400,
-      prohibited_till = 400
+      prohibited_till = 400,
+      weak_ai_limit = 1,
+      normal_ai_limit = 2
    },
 
    working_positions = {

=== modified file 'tribes/buildings/productionsites/empire/winery/init.lua'
--- tribes/buildings/productionsites/empire/winery/init.lua	2015-10-31 12:11:44 +0000
+++ tribes/buildings/productionsites/empire/winery/init.lua	2015-11-24 21:27:31 +0000
@@ -36,7 +36,9 @@
 
    aihints = {
       forced_after = 600,
-      prohibited_till = 600
+      prohibited_till = 600,
+      weak_ai_limit = 1,
+      normal_ai_limit = 2
    },
 
    working_positions = {

=== modified file 'tribes/buildings/trainingsites/atlanteans/dungeon/init.lua'
--- tribes/buildings/trainingsites/atlanteans/dungeon/init.lua	2015-10-31 12:11:44 +0000
+++ tribes/buildings/trainingsites/atlanteans/dungeon/init.lua	2015-11-24 21:27:31 +0000
@@ -36,7 +36,9 @@
    },
 
    aihints = {
-      trainingsite_type = "advanced"
+      trainingsite_type = "advanced",
+      weak_ai_limit = 0,
+      normal_ai_limit = 1
    },
 
    working_positions = {

=== modified file 'tribes/buildings/trainingsites/atlanteans/labyrinth/init.lua'
--- tribes/buildings/trainingsites/atlanteans/labyrinth/init.lua	2015-10-31 12:11:44 +0000
+++ tribes/buildings/trainingsites/atlanteans/labyrinth/init.lua	2015-11-24 21:27:31 +0000
@@ -36,7 +36,9 @@
 
    aihints = {
       prohibited_till=900,
-      trainingsite_type = "basic"
+      trainingsite_type = "basic",
+      weak_ai_limit = 1,
+      normal_ai_limit = 2
    },
 
    working_positions = {

=== modified file 'tribes/buildings/trainingsites/barbarians/battlearena/init.lua'
--- tribes/buildings/trainingsites/barbarians/battlearena/init.lua	2015-10-31 12:11:44 +0000
+++ tribes/buildings/trainingsites/barbarians/battlearena/init.lua	2015-11-24 21:27:31 +0000
@@ -52,7 +52,9 @@
 
    aihints = {
       prohibited_till = 2700,
-      trainingsite_type = "basic"
+      trainingsite_type = "basic",
+      weak_ai_limit = 1,
+      normal_ai_limit = 3
    },
 
    working_positions = {

=== modified file 'tribes/buildings/trainingsites/barbarians/trainingcamp/init.lua'
--- tribes/buildings/trainingsites/barbarians/trainingcamp/init.lua	2015-10-31 12:11:44 +0000
+++ tribes/buildings/trainingsites/barbarians/trainingcamp/init.lua	2015-11-24 21:27:31 +0000
@@ -45,7 +45,9 @@
 
    aihints = {
       prohibited_till = 500,
-      trainingsite_type = "advanced"
+      trainingsite_type = "advanced",
+      weak_ai_limit = 0,
+      normal_ai_limit = 1
    },
 
    working_positions = {

=== modified file 'tribes/buildings/trainingsites/empire/arena/init.lua'
--- tribes/buildings/trainingsites/empire/arena/init.lua	2015-10-31 12:11:44 +0000
+++ tribes/buildings/trainingsites/empire/arena/init.lua	2015-11-24 21:27:31 +0000
@@ -40,7 +40,9 @@
    },
 
    aihints = {
-      trainingsite_type = "basic"
+      trainingsite_type = "basic",
+      weak_ai_limit = 1,
+      normal_ai_limit = 2
    },
 
    working_positions = {

=== modified file 'tribes/buildings/trainingsites/empire/colosseum/init.lua'
--- tribes/buildings/trainingsites/empire/colosseum/init.lua	2015-10-31 12:11:44 +0000
+++ tribes/buildings/trainingsites/empire/colosseum/init.lua	2015-11-24 21:27:31 +0000
@@ -34,7 +34,9 @@
    },
 
    aihints = {
-      trainingsite_type = "basic"
+      trainingsite_type = "basic",
+      weak_ai_limit = 1,
+      normal_ai_limit = 2
    },
 
    working_positions = {

=== modified file 'tribes/buildings/trainingsites/empire/trainingcamp/init.lua'
--- tribes/buildings/trainingsites/empire/trainingcamp/init.lua	2015-10-31 12:11:44 +0000
+++ tribes/buildings/trainingsites/empire/trainingcamp/init.lua	2015-11-24 21:27:31 +0000
@@ -36,7 +36,9 @@
 
    aihints = {
       prohibited_till = 2700,
-      trainingsite_type = "advanced"
+      trainingsite_type = "advanced",
+      weak_ai_limit = 0,
+      normal_ai_limit = 1
    },
 
    working_positions = {


Follow ups