#include "BananaBrain.h"

void OpponentModel::init()
{
	Race::set races = Broodwar->enemies().getRaces();
	if (races.size() == 1) {
		Race race = *races.begin();
		if (race_known(race)) initial_enemy_race_ = enemy_race_ = race;
	}
}

std::string OpponentModel::enemy_opening_info() const
{
	switch (enemy_opening_) {
		// Zerg
		case EnemyOpening::Z_4_5Pool:
			return "Z_4/5pool";
		case EnemyOpening::Z_9Pool:
			return "Z_9pool";
		case EnemyOpening::Z_9PoolSpeed:
			return "Z_9poolspeed";
		case EnemyOpening::Z_OverPool:
			return "Z_overpool";
		case EnemyOpening::Z_12Pool:
			return "Z_12pool";
		case EnemyOpening::Z_10Hatch:
			return "Z_10hatch";
		case EnemyOpening::Z_12Hatch:
			return "Z_12hatch";
		case EnemyOpening::Z_12HatchMain:
			return "Z_12hatchmain";
			
		// Terran
		case EnemyOpening::T_BBS:
			return "T_bbs";
		case EnemyOpening::T_2Rax:
			return "T_2rax";
		case EnemyOpening::T_1Fac:
			return "T_1fac";
		case EnemyOpening::T_2Fac:
			return "T_2fac";
		case EnemyOpening::T_FastExpand:
			return "T_fastexpand";
		case EnemyOpening::T_WallIn:
			return "T_wallin";
			
		// Protoss
		case EnemyOpening::P_1GateCore:
			return "P_1gatecore";
		case EnemyOpening::P_4GateGoon:
			return "P_4gategoon";
		case EnemyOpening::P_2Gate:
			return "P_2gate";
		case EnemyOpening::P_2GateFast:
			return "P_2gatefast";
		case EnemyOpening::P_ProxyGate:
			return "P_proxygate";
		case EnemyOpening::P_FastExpand:
			return "P_fastexpand";
		case EnemyOpening::P_ForgeFastExpand:
			return "P_ffe";
		case EnemyOpening::P_CannonRush:
			return "P_cannonrush";
		case EnemyOpening::P_CannonTurtle:
			return "P_cannonturtle";
	}
	switch (enemy_race_) {
		case Races::Zerg:
			return "Z_unknown";
		case Races::Terran:
			return "T_unknown";
		case Races::Protoss:
			return "P_unknown";
		default:
			return "unknown";
	}
}

int OpponentModel::enemy_latest_expansion_frame()
{
	int result;
	
	switch (enemy_race_) {
		case Races::Zerg:
			result = k12HatchHatcheryFrame;
			break;
		case Races::Terran:
			result = k14CCCommandCenterFrame;
			break;
		case Races::Protoss:
			result = k12NexusNexusFrame;
			break;
		default:
			result = std::max(k12HatchHatcheryFrame, std::max(k14CCCommandCenterFrame, k12NexusNexusFrame));
			break;
	}
	
	return result;
}

bool OpponentModel::cloaked_or_mine_present() const
{
	return cloaked_present_ || information_manager.enemy_seen(UnitTypes::Terran_Vulture_Spider_Mine);
}

bool OpponentModel::position_not_in_main(Position enemy_position)
{
	if (!enemy_position.isValid()) return false;
	
	const BWEM::Area* area = area_at(enemy_position);
	if (area != nullptr) {
		for (auto& base : area->Bases()) {
			if (base.Starting()) return false;
		}
	}
	
	return true;
}

bool OpponentModel::position_in_enemy_main(Position enemy_position)
{
	if (!enemy_position.isValid()) return false;
	
	const BWEM::Area* area = area_at(enemy_position);
	if (area != nullptr) {
		for (auto& base : area->Bases()) {
			if (base.Starting() && base_state.controlled_and_planned_bases().count(&base) == 0) return true;
		}
	}
	
	return false;
}

bool OpponentModel::position_in_main_near_choke(Position enemy_position,int max_distance)
{
	bool result = false;
	const BWEM::Area* area = area_at(enemy_position);
	std::vector<const BWEM::Base*> potential_start_bases = tactics_manager.possible_enemy_start_bases();
	for (auto& potential_start_base : potential_start_bases) {
		const BWEM::Area* base_area = potential_start_base->GetArea();
		if (base_area == area) {
			for (auto& cp : base_area->ChokePoints()) {
				if (!cp->Blocked() && chokepoint_center(cp).getApproxDistance(enemy_position) <= max_distance) {
					result = true;
					break;
				}
			}
			if (result) break;
		}
	}
	return result;
}

bool OpponentModel::position_in_or_near_potential_natural(Position enemy_position,int max_distance)
{
	bool result = false;
	const BWEM::Area* area = area_at(enemy_position);
	std::vector<const BWEM::Base*> potential_start_bases = tactics_manager.possible_enemy_start_bases();
	for (auto& potential_start_base : potential_start_bases) {
		const BWEM::Base* potential_natural = base_state.natural_base_for_start_base(potential_start_base);
		if (potential_natural != nullptr) {
			const BWEM::Area* base_area = potential_natural->GetArea();
			if (base_area == area) {
				result = true;
				break;
			}
			
			for (auto& cp : base_area->ChokePoints()) {
				if (!cp->Blocked() && chokepoint_center(cp).getApproxDistance(enemy_position) <= max_distance) {
					result = true;
					break;
				}
			}
		}
	}
	return result;
}

bool OpponentModel::is_non_start_hatchery(Unit unit)
{
	bool result = false;
	
	if (unit->getType() == UnitTypes::Zerg_Hatchery) {
		const BWEM::Base* base = base_state.base_for_tile_position(unit->getTilePosition());
		result = (base == nullptr || !base->Starting());
	}
	
	return result;
}

void OpponentModel::update_state_for_unit(Unit unit)
{
	UnitType type = unit->getType();
	if (!air_to_ground_present_ && is_air_to_ground(unit)) air_to_ground_present_ = true;
	if (!cloaked_present_ && is_cloaked(unit)) cloaked_present_ = true;
	if (enemy_opening_ == EnemyOpening::Unknown) {
		if (unit->getType() == UnitTypes::Zerg_Spawning_Pool) {
			update_state_for_spawning_pool(unit);
		} else if (unit->getType() == UnitTypes::Zerg_Hatchery) {
			update_state_for_hatchery(unit);
		} else if (unit->getType() == UnitTypes::Zerg_Zergling) {
			update_state_for_zergling(unit);
		} else if (is_cannon_rush(unit)) {
			enemy_opening_ = EnemyOpening::P_CannonRush;
		} else if (is_proxy_gate(unit)) {
			enemy_opening_ = EnemyOpening::P_ProxyGate;
		}
	}
	if ((enemy_opening_ == EnemyOpening::Unknown || enemy_opening_ == EnemyOpening::P_1GateCore) &&
		unit->getType() == UnitTypes::Protoss_Dragoon) {
		update_state_for_dragoon(unit);
	}
	if (enemy_opening_ == EnemyOpening::Z_9Pool && unit->getType() == UnitTypes::Zerg_Extractor && !unit->isCompleted()) {
		int start_frame = estimate_building_start_frame_based_on_hit_points(unit);
		if (start_frame <= k9PoolSpeedExtractorFrame && unit->getHitPoints() > UnitTypes::Zerg_Extractor.maxHitPoints() / 3) {
			enemy_opening_ = EnemyOpening::Z_9PoolSpeed;
		}
	}
	if ((enemy_opening_ == EnemyOpening::Unknown || enemy_opening_ == EnemyOpening::Z_9Pool) &&
		unit->getType() == UnitTypes::Zerg_Zergling &&
		Broodwar->getFrameCount() <= k9PoolSpeedMetabolicBoostFrame + UpgradeTypes::Metabolic_Boost.upgradeTime() + 500 &&
		information_manager.upgrade_level(unit->getPlayer(), UpgradeTypes::Metabolic_Boost) > 0) {
		enemy_opening_ = EnemyOpening::Z_9PoolSpeed;
	}
	if (enemy_opening_ == EnemyOpening::Z_12Hatch &&
		unit->getType() == UnitTypes::Zerg_Hatchery &&
		!unit->isCompleted() &&
		is_non_start_hatchery(unit) &&
		position_in_enemy_main(unit->getPosition()) &&
		estimate_building_start_frame_based_on_hit_points(unit) <= (k12HatchHatcheryFrame + k9PoolHatcheryFrame) / 2) {
		enemy_opening_ = EnemyOpening::Z_12HatchMain;
	}
	if (!non_basic_combat_unit_seen_ && !type.isBuilding() && !type.isWorker() &&
		type != UnitTypes::Terran_Marine && type != UnitTypes::Zerg_Zergling && type != UnitTypes::Protoss_Zealot &&
		type != UnitTypes::Zerg_Overlord && type != UnitTypes::Zerg_Larva && type != UnitTypes::Zerg_Egg) {
		non_basic_combat_unit_seen_ = true;
	}
}

bool OpponentModel::is_air_to_ground(Unit unit)
{
	return unit->isFlying() && (can_attack(unit, false) || is_spellcaster(unit->getType()));
}

bool OpponentModel::is_cloaked(Unit unit)
{
	UnitType type = unit->getType();
	return (type != UnitTypes::Terran_Vulture_Spider_Mine &&
			type != UnitTypes::Protoss_Observer &&
			(unit->isCloaked() ||
			 unit->isBurrowed() ||
			 type.hasPermanentCloak() ||
			 type == UnitTypes::Zerg_Lurker ||
			 type == UnitTypes::Zerg_Lurker_Egg));
}

void OpponentModel::update_state_for_spawning_pool(Unit unit)
{
	int start_frame = estimate_building_start_frame_based_on_hit_points(unit);
	
	if (start_frame < (k5PoolSpawningPoolFrame + k9PoolSpawningPoolFrame) / 2) {
		enemy_opening_ = EnemyOpening::Z_4_5Pool;
	} else if (!unit->isCompleted()) {
		if (start_frame < (k9PoolSpawningPoolFrame + kOverPoolSpawningPoolFrame) / 2) {
			enemy_opening_ = EnemyOpening::Z_9Pool;
		} else if (start_frame < (kOverPoolSpawningPoolFrame + k12PoolSpawningPoolFrame) / 2) {
			enemy_opening_ = EnemyOpening::Z_OverPool;
		} else if (start_frame < (k12PoolSpawningPoolFrame + k10HatchSpawningPoolFrame) / 2) {
			enemy_opening_ = EnemyOpening::Z_12Pool;
		} else if (start_frame < (k10HatchSpawningPoolFrame + k12HatchSpawningPoolFrame) / 2) {
			enemy_opening_ = EnemyOpening::Z_10Hatch;
		} else if (start_frame < k12HatchSpawningPoolFrame + 500) {
			enemy_opening_ = EnemyOpening::Z_12Hatch;
		}
	}
}

void OpponentModel::update_state_for_hatchery(Unit unit)
{
	int start_frame = estimate_building_start_frame_based_on_hit_points(unit);
	
	if (is_non_start_hatchery(unit) && start_frame <= (k10HatchHatcheryFrame + k12HatchHatcheryFrame) / 2) {
		enemy_opening_ = EnemyOpening::Z_10Hatch;
	} else if (!unit->isCompleted()) {
		if (position_not_in_main(unit->getPosition())) {
			if (start_frame <= (k12HatchHatcheryFrame + kOverPoolHatcheryFrame) / 2) {
				enemy_opening_ = EnemyOpening::Z_12Hatch;
			} else if (start_frame <= (kOverPoolHatcheryFrame + k9PoolHatcheryFrame) / 2) {
				enemy_opening_ = EnemyOpening::Z_OverPool;
			} else if (start_frame <= k9PoolHatcheryFrame + 500) {
				enemy_opening_ = EnemyOpening::Z_9Pool;
			}
		} else if (is_non_start_hatchery(unit) &&
				   information_manager.enemy_count(UnitTypes::Zerg_Drone) > 9) {
			if (start_frame <= (k12HatchHatcheryFrame + kOverPoolHatcheryFrame) / 2) {
				enemy_opening_ = EnemyOpening::Z_12HatchMain;
			}
		}
	}
}

void OpponentModel::update_state_for_zergling(Unit unit)
{
	std::vector<const BWEM::Base*> bases = tactics_manager.possible_enemy_start_bases();
	int min_distance = INT_MAX;
	for (auto& base : bases) {
		int distance = ground_distance(unit->getPosition(), base->Center());
		if (distance > 0) min_distance = std::min(min_distance, distance);
	}
	if (min_distance < INT_MAX) {
		int depart_frame = int(Broodwar->getFrameCount() - min_distance / UnitTypes::Zerg_Zergling.topSpeed() + 0.5);
		if (depart_frame <= k9PoolZerglingFrame - 50) {
			enemy_opening_ = EnemyOpening::Z_4_5Pool;
		} else if (depart_frame <= (k9PoolZerglingFrame + kOverPoolZerglingFrame) / 2) {
			enemy_opening_ = EnemyOpening::Z_9Pool;
		} else if (depart_frame <= (kOverPoolZerglingFrame + k12PoolZerglingFrame) / 2) {
			enemy_opening_ = EnemyOpening::Z_OverPool;
		}
	}
}

void OpponentModel::update_state_for_dragoon(Unit unit)
{
	int dragoon_count = information_manager.enemy_seen_count(UnitTypes::Protoss_Dragoon);
	if (dragoon_count >= 7) {
		std::vector<const BWEM::Base*> bases = tactics_manager.possible_enemy_start_bases();
		int min_distance = INT_MAX;
		for (auto& base : bases) {
			int distance = ground_distance(unit->getPosition(), base->Center());
			if (distance > 0) min_distance = std::min(min_distance, distance);
		}
		if (min_distance < INT_MAX) {
			int depart_frame = int(Broodwar->getFrameCount() - min_distance / UnitTypes::Protoss_Dragoon.topSpeed() + 0.5);
			if ((dragoon_count >= 7 && depart_frame <= k4GateGoon7Frame) ||
				(dragoon_count >= 11 && depart_frame <= k4GateGoon11Frame) ||
				(dragoon_count >= 15 && depart_frame <= k4GateGoon15Frame)) {
				enemy_opening_ = EnemyOpening::P_4GateGoon;
			}
		}
	}
}

bool OpponentModel::is_cannon_rush(Unit unit)
{
	return (unit->getType() == UnitTypes::Protoss_Photon_Cannon &&
			estimate_building_start_frame_based_on_hit_points(unit) <= 15 * UnitTypes::Protoss_Probe.buildTime() &&
			information_manager.all_units().at(unit).base_distance <= 512);
}

bool OpponentModel::is_proxy_gate(Unit unit)
{
	return (unit->getType() == UnitTypes::Protoss_Gateway &&
			estimate_building_start_frame_based_on_hit_points(unit) <= k99GateSecondGatewayFrame + UnitTypes::Protoss_Gateway.buildTime() &&
			!position_in_enemy_main(unit->getPosition()) &&
			!position_in_or_near_potential_natural(unit->getPosition()));
}

void OpponentModel::update_state_for_unit(const InformationUnit& unit)
{
	if ((enemy_opening_ == EnemyOpening::Unknown || enemy_opening_ == EnemyOpening::P_CannonRush) &&
		is_cannon_turtle(unit)) {
		enemy_opening_ = EnemyOpening::P_CannonTurtle;
	}
	if ((enemy_opening_ == EnemyOpening::Unknown || enemy_opening_ == EnemyOpening::P_FastExpand) && is_forge_fast_expand(unit)) {
		enemy_opening_ = EnemyOpening::P_ForgeFastExpand;
	}
	if (enemy_opening_ == EnemyOpening::Unknown && unit.type.isResourceDepot() && !unit.flying) {
		update_state_for_resource_depot(unit);
	}
}

void OpponentModel::update_state_for_resource_depot(const InformationUnit& unit)
{
	const BWEM::Base* base = base_state.base_for_tile_position(unit.tile_position());
	if (base == nullptr || !base->Starting()) {
		if (unit.type.getRace() == Races::Terran && unit.start_frame <= k14CCCommandCenterFrame + 500) {
			enemy_opening_ = EnemyOpening::T_FastExpand;
		} else if (unit.type.getRace() == Races::Protoss && unit.start_frame <= k12NexusNexusFrame + 500) {
			enemy_opening_ = EnemyOpening::P_FastExpand;
		}
	}
}

bool OpponentModel::is_cannon_turtle(const InformationUnit& unit)
{
	return (unit.type == UnitTypes::Protoss_Photon_Cannon &&
			!enemy_natural_sufficiently_scouted_ &&
			position_in_main_near_choke(unit.position, WeaponTypes::STS_Photon_Cannon.maxRange()));
}

bool OpponentModel::is_forge_fast_expand(const InformationUnit& unit)
{
	return ((unit.type == UnitTypes::Protoss_Photon_Cannon || unit.type == UnitTypes::Protoss_Forge) &&
			!enemy_natural_sufficiently_scouted_ &&
			unit.base_distance > 512 &&
			position_not_in_main(unit.position) &&
			position_in_or_near_potential_natural(unit.position));
}

void OpponentModel::update_emp_seen()
{
	if (!emp_seen_) {
		emp_seen_ = std::any_of(Broodwar->getBullets().begin(), Broodwar->getBullets().end(), [](auto& bullet){
			return bullet->getType() == BulletTypes::EMP_Missile;
		});
	}
}

void OpponentModel::update_blocked_expansion_seen()
{
	if (!blocked_expansion_seen_) {
		blocked_expansion_seen_ = std::any_of(worker_manager.worker_map().begin(),
											  worker_manager.worker_map().end(),
											  [](auto& entry) {
												  const Worker& worker = entry.second;
												  return worker.order()->need_detection();
											  });
	}
}

void OpponentModel::update()
{
	update_emp_seen();
	update_blocked_expansion_seen();
	
	if (Broodwar->enemies().size() == 1) {
		for (auto& unit : Broodwar->enemies().getUnits()) {
			if (unit->exists()) {
				if (!enemy_race_known() && race_known(unit->getType().getRace())) enemy_race_ = unit->getType().getRace();
				update_state_for_unit(unit);
			}
		}
	}
	
	for (auto& enemy_unit : information_manager.enemy_units()) {
		update_state_for_unit(*enemy_unit);
	}
	
	update_enemy_base_sufficiently_scouted();
	
	if (enemy_opening_ == EnemyOpening::Unknown && enemy_race_ == Races::Protoss) {
		int gateway_99_count = 0;
		int gateway_1012_count = 0;
		int gateway_count = 0;
		int cybercore_count = 0;
		int assimilator_count = 0;
		int forge_count = 0;
		int forge_cannon_rush_count = 0;
		int forge_main_count = 0;
		int cannon_main_count = 0;
		
		for (auto& enemy_unit : information_manager.enemy_units()) {
			if (enemy_unit->type == UnitTypes::Protoss_Gateway) {
				if (enemy_unit->start_frame <= (k99GateSecondGatewayFrame + k1012GateSecondGatewayFrame) / 2) gateway_99_count++;
				if (enemy_unit->start_frame <= k1012GateSecondGatewayFrame + 500) gateway_1012_count++;
				gateway_count++;
			}
			if (enemy_unit->type == UnitTypes::Protoss_Cybernetics_Core) cybercore_count++;
			if (enemy_unit->type == UnitTypes::Protoss_Assimilator) assimilator_count++;
			if (enemy_unit->type == UnitTypes::Protoss_Forge) {
				if (position_in_enemy_main(enemy_unit->position)) {
					if (enemy_unit->start_frame <= kCannonRushForgeFrame + 500) forge_cannon_rush_count++;
					forge_main_count++;
				}
				forge_count++;
			}
			if (enemy_unit->type == UnitTypes::Protoss_Photon_Cannon && position_in_enemy_main(enemy_unit->position)) {
				cannon_main_count++;
			}
		}
		
		if (gateway_99_count >= 2) {
			enemy_opening_ = EnemyOpening::P_2GateFast;
		} else if (gateway_1012_count >= 2) {
			enemy_opening_ = EnemyOpening::P_2Gate;
		} else if (gateway_count == 1 && (assimilator_count > 0 || cybercore_count > 0) &&
				   Broodwar->getFrameCount() < k1012GateSecondGatewayFrame) {
			enemy_opening_ = EnemyOpening::P_1GateCore;
		} else if (enemy_base_sufficiently_scouted() && gateway_count == 1 &&
				   Broodwar->getFrameCount() >= k1012GateSecondGatewayFrame + 500) {
			enemy_opening_ = EnemyOpening::P_1GateCore;
		} else if (enemy_base_sufficiently_scouted_ && enemy_natural_sufficiently_scouted_ &&
				   Broodwar->getFrameCount() >= k1012GateFirstGatewayFrame + UnitTypes::Protoss_Gateway.buildTime() &&
				   gateway_count == 0 && forge_count == 0 && cybercore_count == 0 && assimilator_count == 0) {
			enemy_opening_ = EnemyOpening::P_ProxyGate;
		} else if ((forge_cannon_rush_count >= 1 && cannon_main_count == 0 && Broodwar->getFrameCount() >= kCannonRushForgeFrame + UnitTypes::Protoss_Forge.buildTime() + 120) ||
				   (enemy_base_sufficiently_scouted_ && gateway_count == 0 && forge_main_count >= 1 && cannon_main_count == 0 && cybercore_count == 0 && assimilator_count == 0)) {
			enemy_opening_ = EnemyOpening::P_CannonRush;
		}
	}

	if ((enemy_opening_ == EnemyOpening::Unknown || enemy_opening_ == EnemyOpening::P_1GateCore)
		&& enemy_race_ == Races::Protoss) {
		int gateway_count = 0;
		int assimilator_count = 0;
		int cybercore_count = 0;
		
		for (auto& enemy_unit : information_manager.enemy_units()) {
			if (enemy_unit->start_frame <= k4GateGoonBuildingsFrame) {
				if (enemy_unit->type == UnitTypes::Protoss_Gateway) gateway_count++;
				if (enemy_unit->type == UnitTypes::Protoss_Assimilator) assimilator_count++;
				if (enemy_unit->type == UnitTypes::Protoss_Cybernetics_Core) cybercore_count++;
			}
		}
		
		if (gateway_count >= 4 && assimilator_count >= 1 && cybercore_count >= 1) enemy_opening_ = EnemyOpening::P_4GateGoon;
	}

	if (enemy_opening_ == EnemyOpening::Unknown && enemy_race_ == Races::Terran) {
		int bbs_barracks_count = 0;
		int barracks_count = 0;
		int factory_count = 0;
		
		for (auto& enemy_unit : information_manager.enemy_units()) {
			if (enemy_unit->type == UnitTypes::Terran_Barracks) {
				if (enemy_unit->start_frame <= (kBBSFirstBarracksFrame + k2RaxSecondBarracksFrame) / 2) bbs_barracks_count++;
				if (enemy_unit->start_frame <= k2RaxSecondBarracksFrame + 500) barracks_count++;
			} else if (enemy_unit->type == UnitTypes::Terran_Factory) {
				if (enemy_unit->start_frame <= k2FacSecondFactoryFrame + 500) factory_count++;
			}
		}
		
		if (bbs_barracks_count >= 2) {
			enemy_opening_ = EnemyOpening::T_BBS;
		} else if (barracks_count >= 2) {
			enemy_opening_ = EnemyOpening::T_2Rax;
		} else if (factory_count >= 2) {
			enemy_opening_ = EnemyOpening::T_2Fac;
		} else if (enemy_base_sufficiently_scouted_ &&
				   factory_count >= 1 &&
				   Broodwar->getFrameCount() >= k2FacSecondFactoryFrame + 500) {
			enemy_opening_ = EnemyOpening::T_1Fac;
		}
	}
	
	if (enemy_opening_ == EnemyOpening::Unknown &&
		enemy_race_ == Races::Terran &&
		!base_state.is_island_map() &&
		Broodwar->getFrameCount() <= kTerranWallInDetectUntilFrame) {
		std::vector<const BWEM::Base*> bases = tactics_manager.possible_enemy_start_bases();
		std::set<int> components_start = connectivity_grid.building_components(Broodwar->self()->getRace().getResourceDepot(), base_state.start_base()->Location());
		for (auto& base : bases) {
			if (!common_elements(components_start, connectivity_grid.building_components(UnitTypes::Terran_Command_Center, base->Location()))) {
				enemy_opening_ = EnemyOpening::T_WallIn;
				break;
			}
		}
	}
}

void OpponentModel::update_enemy_base_sufficiently_scouted()
{
	if (!enemy_base_sufficiently_scouted_) {
		if (worker_manager.is_scouting() &&
			tactics_manager.enemy_start_base() != nullptr &&
			Broodwar->getFrameCount() - tactics_manager.enemy_start_base_found_at_frame() > 320 &&
			resources_explored(tactics_manager.enemy_start_base())) {
			enemy_base_sufficiently_scouted_ = true;
		}
	}
	
	if (enemy_natural_sufficiently_scouted_start_frame_ == -1) {
		if (tactics_manager.enemy_start_base() != nullptr && tactics_manager.enemy_natural_base() == nullptr) {
			enemy_natural_sufficiently_scouted_ = true;
		} else if (tactics_manager.enemy_natural_base() != nullptr) {
			int expansion_frame = enemy_latest_expansion_frame();
			int seen_frame = base_state.base_last_seen(tactics_manager.enemy_natural_base());
			if (seen_frame > expansion_frame + 240) {
				enemy_natural_sufficiently_scouted_start_frame_ = Broodwar->getFrameCount();
			}
		}
	} else if (enemy_natural_sufficiently_scouted_start_frame_ + 100 >= Broodwar->getFrameCount()) {
		enemy_natural_sufficiently_scouted_ = true;
	}
}
