def _print_empire_species_roster(): """Print empire species roster in table format to log.""" grade_map = {"ULTIMATE": "+++", "GREAT": "++", "GOOD": "+", "AVERAGE": "o", "BAD": "-", "NO": "---"} grade_tags = [ Tags.INDUSTRY, Tags.RESEARCH, Tags.POPULATION, Tags.SUPPLY, Tags.WEAPONS, Tags.ATTACKTROOPS, ] grade_tags_names = { Tags.INDUSTRY: "Ind.", Tags.RESEARCH: "Res.", Tags.POPULATION: "Pop.", Tags.SUPPLY: "Supply", Tags.WEAPONS: "Pilot", Tags.ATTACKTROOPS: "Troop", } species_table = Table( Text("species"), Sequence("PIDs"), Bool("Colonize"), Text("Shipyards"), *[Text(grade_tags_names[v]) for v in grade_tags], Sequence("Tags"), table_name="Empire species roster Turn %d" % fo.currentTurn(), ) for species_name, planet_ids in get_empire_planets_by_species().items(): species_tags = fo.getSpecies(species_name).tags number_of_shipyards = len(get_ship_builder_locations(species_name)) species_table.add_row( species_name, planet_ids, can_build_colony_for_species(species_name), number_of_shipyards, *[grade_map.get(get_species_tag_grade(species_name, tag).upper(), "o") for tag in grade_tags], [tag for tag in species_tags if not any(s in tag for s in grade_tags) and "PEDIA" not in tag], ) species_table.print_table(info)
def get_invasion_fleets(): invasion_timer.start("gathering initial info") universe = fo.getUniverse() empire = fo.getEmpire() empire_id = fo.empireID() home_system_id = PlanetUtilsAI.get_capital_sys_id() aistate = get_aistate() visible_system_ids = list(aistate.visInteriorSystemIDs) + list( aistate.visBorderSystemIDs) if home_system_id != INVALID_ID: accessible_system_ids = [ sys_id for sys_id in visible_system_ids if systems_connected(sys_id, home_system_id) ] else: debug( "Empire has no identifiable homeworld; will treat all visible planets as accessible." ) # TODO: check if any troop ships owned, use their system as home system accessible_system_ids = visible_system_ids acessible_planet_ids = PlanetUtilsAI.get_planets_in__systems_ids( accessible_system_ids) all_owned_planet_ids = PlanetUtilsAI.get_all_owned_planet_ids( acessible_planet_ids) # includes unpopulated outposts all_populated_planets = PlanetUtilsAI.get_populated_planet_ids( acessible_planet_ids) # includes unowned natives empire_owned_planet_ids = PlanetUtilsAI.get_owned_planets_by_empire( universe.planetIDs) invadable_planet_ids = set(all_owned_planet_ids).union( all_populated_planets) - set(empire_owned_planet_ids) invasion_targeted_planet_ids = get_invasion_targeted_planet_ids( universe.planetIDs, MissionType.INVASION) invasion_targeted_planet_ids.extend( get_invasion_targeted_planet_ids(universe.planetIDs, MissionType.ORBITAL_INVASION)) all_invasion_targeted_system_ids = set( PlanetUtilsAI.get_systems(invasion_targeted_planet_ids)) invasion_fleet_ids = FleetUtilsAI.get_empire_fleet_ids_by_role( MissionType.INVASION) num_invasion_fleets = len( FleetUtilsAI.extract_fleet_ids_without_mission_types( invasion_fleet_ids)) debug("Current Invasion Targeted SystemIDs: %s" % PlanetUtilsAI.sys_name_ids(AIstate.invasionTargetedSystemIDs)) debug("Current Invasion Targeted PlanetIDs: %s" % PlanetUtilsAI.planet_string(invasion_targeted_planet_ids)) debug(invasion_fleet_ids and "Invasion Fleet IDs: %s" % invasion_fleet_ids or "Available Invasion Fleets: 0") debug("Invasion Fleets Without Missions: %s" % num_invasion_fleets) invasion_timer.start("planning troop base production") reserved_troop_base_targets = [] if aistate.character.may_invade_with_bases(): available_pp = {} for el in empire.planetsWithAvailablePP: # keys are sets of ints; data is doubles avail_pp = el.data() for pid in el.key(): available_pp[pid] = avail_pp # For planning base trooper invasion targets we have a two-pass system. (1) In the first pass we consider all # the invasion targets and figure out which ones appear to be suitable for using base troopers against (i.e., we # already have a populated planet in the same system that could build base troopers) and we have at least a # minimal amount of PP available, and (2) in the second pass we go through the reserved base trooper target list # and check to make sure that there does not appear to be too much military action still needed before the # target is ready to be invaded, we double check that not too many base troopers would be needed, and if things # look clear then we queue up the base troopers on the Production Queue and keep track of where we are building # them, and how many; we may also disqualify and remove previously qualified targets (in case, for example, # we lost our base trooper source planet since it was first added to list). # # For planning and tracking base troopers under construction, we use a dictionary store in # get_aistate().qualifyingTroopBaseTargets, keyed by the invasion target planet ID. We only store values # for invasion targets that appear likely to be suitable for base trooper use, and store a 2-item list. # The first item in this list is the ID of the planet where we expect to build the base troopers, and the second # entry initially is set to INVALID_ID (-1). The presence of this entry in qualifyingTroopBaseTargets # flags this target as being reserved as a base-trooper invasion target. # In the second pass, if/when we actually start construction, then we modify the record, replacing that second # value with the ID of the planet where the troopers are actually being built. (Right now that is always the # same as the source planet originally identified, but we could consider reevaluating that, or use that second # value to instead record how many base troopers have been queued, so that on later turns we can assess if the # process got delayed & perhaps more troopers need to be queued). # Pass 1: identify qualifying base troop invasion targets for pid in invadable_planet_ids: # TODO: reorganize if pid in aistate.qualifyingTroopBaseTargets: continue planet = universe.getPlanet(pid) if not planet: continue sys_id = planet.systemID sys_partial_vis_turn = get_partial_visibility_turn(sys_id) planet_partial_vis_turn = get_partial_visibility_turn(pid) if planet_partial_vis_turn < sys_partial_vis_turn: continue best_base_planet = INVALID_ID best_trooper_count = 0 for pid2 in get_colonized_planets_in_system(sys_id): if available_pp.get( pid2, 0 ) < 2: # TODO: improve troop base PP sufficiency determination break planet2 = universe.getPlanet(pid2) if not planet2 or not can_build_ship_for_species( planet2.speciesName): continue best_base_trooper_here = get_best_ship_info( PriorityType.PRODUCTION_ORBITAL_INVASION, pid2)[1] if not best_base_trooper_here: continue troops_per_ship = best_base_trooper_here.troopCapacity if not troops_per_ship: continue species_troop_grade = get_species_tag_grade( planet2.speciesName, Tags.ATTACKTROOPS) troops_per_ship = CombatRatingsAI.weight_attack_troops( troops_per_ship, species_troop_grade) if troops_per_ship > best_trooper_count: best_base_planet = pid2 best_trooper_count = troops_per_ship if best_base_planet != INVALID_ID: aistate.qualifyingTroopBaseTargets.setdefault( pid, [best_base_planet, INVALID_ID]) # Pass 2: for each target previously identified for base troopers, check that still qualifies and # check how many base troopers would be needed; if reasonable then queue up the troops and record this in # get_aistate().qualifyingTroopBaseTargets for pid in list(aistate.qualifyingTroopBaseTargets.keys()): planet = universe.getPlanet(pid) if planet and planet.owner == empire_id: del aistate.qualifyingTroopBaseTargets[pid] continue if pid in invasion_targeted_planet_ids: # TODO: consider overriding standard invasion mission continue if aistate.qualifyingTroopBaseTargets[pid][1] != -1: reserved_troop_base_targets.append(pid) if planet: all_invasion_targeted_system_ids.add(planet.systemID) # TODO: evaluate changes to situation, any more troops needed, etc. continue # already building for here _, planet_troops = evaluate_invasion_planet(pid) sys_id = planet.systemID this_sys_status = aistate.systemStatus.get(sys_id, {}) troop_tally = 0 for _fid in this_sys_status.get("myfleets", []): troop_tally += FleetUtilsAI.count_troops_in_fleet(_fid) if troop_tally > planet_troops: # base troopers appear unneeded del aistate.qualifyingTroopBaseTargets[pid] continue if planet.currentMeterValue(fo.meterType.shield) > 0 and ( this_sys_status.get("myFleetRating", 0) < 0.8 * this_sys_status.get("totalThreat", 0) or this_sys_status.get("myFleetRatingVsPlanets", 0) < this_sys_status.get("planetThreat", 0)): # this system not secured, so ruling out invasion base troops for now # don't immediately delete from qualifyingTroopBaseTargets or it will be opened up for regular troops continue loc = aistate.qualifyingTroopBaseTargets[pid][0] best_base_trooper_here = get_best_ship_info( PriorityType.PRODUCTION_ORBITAL_INVASION, loc)[1] loc_planet = universe.getPlanet(loc) if best_base_trooper_here is None: # shouldn't be possible at this point, but just to be safe warning( "Could not find a suitable orbital invasion design at %s" % loc_planet) continue # TODO: have TroopShipDesigner give the expected number of troops including species effects directly troops_per_ship = best_base_trooper_here.troopCapacity species_troop_grade = get_species_tag_grade( loc_planet.speciesName, Tags.ATTACKTROOPS) troops_per_ship = CombatRatingsAI.weight_attack_troops( troops_per_ship, species_troop_grade) if not troops_per_ship: warning( "The best orbital invasion design at %s seems not to have any troop capacity." % loc_planet) continue _, col_design, build_choices = get_best_ship_info( PriorityType.PRODUCTION_ORBITAL_INVASION, loc) if not col_design: continue if loc not in build_choices: warning( "Best troop design %s can not be produced at planet with id: %s" % (col_design, build_choices)) continue n_bases = math.ceil( (planet_troops + 1) / troops_per_ship) # TODO: reconsider this +1 safety factor # TODO: evaluate cost and time-to-build of best base trooper here versus cost and time-to-build-and-travel # for best regular trooper elsewhere # For now, we assume what building base troopers is best so long as either (1) we would need no more than # MAX_BASE_TROOPERS_POOR_INVADERS base troop ships, or (2) our base troopers have more than 1 trooper per # ship and we would need no more than MAX_BASE_TROOPERS_GOOD_INVADERS base troop ships if n_bases > MAX_BASE_TROOPERS_POOR_INVADERS or ( troops_per_ship > 1 and n_bases > MAX_BASE_TROOPERS_GOOD_INVADERS): debug( "ruling out base invasion troopers for %s due to high number (%d) required." % (planet, n_bases)) del aistate.qualifyingTroopBaseTargets[pid] continue debug( "Invasion base planning, need %d troops at %d per ship, will build %d ships." % ((planet_troops + 1), troops_per_ship, n_bases)) retval = fo.issueEnqueueShipProductionOrder(col_design.id, loc) debug("Enqueueing %d Troop Bases at %s for %s" % (n_bases, PlanetUtilsAI.planet_string(loc), PlanetUtilsAI.planet_string(pid))) if retval != 0: all_invasion_targeted_system_ids.add(planet.systemID) reserved_troop_base_targets.append(pid) aistate.qualifyingTroopBaseTargets[pid][1] = loc fo.issueChangeProductionQuantityOrder( empire.productionQueue.size - 1, 1, int(n_bases)) fo.issueRequeueProductionOrder(empire.productionQueue.size - 1, 0) invasion_timer.start("evaluating target planets") # TODO: check if any invasion_targeted_planet_ids need more troops assigned evaluated_planet_ids = list( set(invadable_planet_ids) - set(invasion_targeted_planet_ids) - set(reserved_troop_base_targets)) evaluated_planets = assign_invasion_values(evaluated_planet_ids) sorted_planets = [(pid, pscore % 10000, ptroops) for pid, (pscore, ptroops) in evaluated_planets.items()] sorted_planets.sort(key=lambda x: x[1], reverse=True) sorted_planets = [(pid, pscore % 10000, ptroops) for pid, pscore, ptroops in sorted_planets] invasion_table = Table( Text("Planet"), Number("Score"), Text("Species"), Number("Troops"), table_name="Potential Targets for Invasion Turn %d" % fo.currentTurn(), ) for pid, pscore, ptroops in sorted_planets: planet = universe.getPlanet(pid) invasion_table.add_row(planet, pscore, planet and planet.speciesName or "unknown", ptroops) invasion_table.print_table(info) sorted_planets = [x for x in sorted_planets if x[1] > 0] # export opponent planets for other AI modules AIstate.opponentPlanetIDs = [pid for pid, __, __ in sorted_planets] AIstate.invasionTargets = sorted_planets # export invasion targeted systems for other AI modules AIstate.invasionTargetedSystemIDs = list(all_invasion_targeted_system_ids) invasion_timer.stop(section_name="evaluating %d target planets" % (len(evaluated_planet_ids))) invasion_timer.stop_print_and_clear()
def evaluate_invasion_planet(planet_id): """Return the invasion value (score, troops) of a planet.""" universe = fo.getUniverse() empire_id = fo.empireID() detail = [] planet = universe.getPlanet(planet_id) if planet is None: debug("Invasion AI couldn't access any info for planet id %d" % planet_id) return [0, 0] system_id = planet.systemID # by using the following instead of simply relying on stealth meter reading, # can (sometimes) plan ahead even if planet is temporarily shrouded by an ion storm predicted_detectable = EspionageAI.colony_detectable_by_empire( planet_id, empire=fo.empireID(), default_result=False) if not predicted_detectable: if get_partial_visibility_turn(planet_id) < fo.currentTurn(): debug("InvasionAI predicts planet id %d to be stealthed" % planet_id) return [0, 0] else: debug( "InvasionAI predicts planet id %d to be stealthed" % planet_id + ", but somehow have current visibility anyway, will still consider as target" ) # Check if the target planet was extra-stealthed somehow its system was last viewed # this test below may augment the tests above, # but can be thrown off by temporary combat-related sighting system_last_seen = get_partial_visibility_turn(planet_id) planet_last_seen = get_partial_visibility_turn(system_id) if planet_last_seen < system_last_seen: # TODO: track detection strength, order new scouting when it goes up debug( "Invasion AI considering planet id %d (stealthed at last view), still proceeding." % planet_id) # get a baseline evaluation of the planet as determined by ColonisationAI species_name = planet.speciesName species = fo.getSpecies(species_name) empire_research_list = tuple(element.tech for element in fo.getEmpire().researchQueue) if not species or AIDependencies.TAG_DESTROYED_ON_CONQUEST in species.tags: # this call iterates over this Empire's available species with which it could colonize after an invasion planet_eval = ColonisationAI.assign_colonisation_values( [planet_id], MissionType.INVASION, None, detail) colony_base_value = max( 0.75 * planet_eval.get(planet_id, [0])[0], calculate_planet_colonization_rating. calculate_planet_colonization_rating( planet_id=planet_id, mission_type=MissionType.OUTPOST, spec_name=None, detail=detail, empire_research_list=empire_research_list, ), ) else: colony_base_value = calculate_planet_colonization_rating.calculate_planet_colonization_rating( planet_id=planet_id, mission_type=MissionType.INVASION, spec_name=species_name, detail=detail, empire_research_list=empire_research_list, ) # Add extra score for all buildings on the planet building_values = { "BLD_IMPERIAL_PALACE": 1000, "BLD_CULTURE_ARCHIVES": 1000, "BLD_AUTO_HISTORY_ANALYSER": 100, "BLD_SHIPYARD_BASE": 100, "BLD_SHIPYARD_ORG_ORB_INC": 200, "BLD_SHIPYARD_ORG_XENO_FAC": 200, "BLD_SHIPYARD_ORG_CELL_GRO_CHAMB": 200, "BLD_SHIPYARD_CON_NANOROBO": 300, "BLD_SHIPYARD_CON_GEOINT": 400, "BLD_SHIPYARD_CON_ADV_ENGINE": 1000, "BLD_SHIPYARD_AST": 300, "BLD_SHIPYARD_AST_REF": 1000, "BLD_SHIPYARD_ENRG_SOLAR": 1500, "BLD_INDUSTRY_CENTER": 500, "BLD_GAS_GIANT_GEN": 200, "BLD_SOL_ORB_GEN": 800, "BLD_BLACK_HOLE_POW_GEN": 2000, "BLD_ENCLAVE_VOID": 500, "BLD_NEUTRONIUM_EXTRACTOR": 2000, "BLD_NEUTRONIUM_SYNTH": 2000, "BLD_NEUTRONIUM_FORGE": 1000, "BLD_CONC_CAMP": 100, "BLD_BIOTERROR_PROJECTOR": 1000, "BLD_SHIPYARD_ENRG_COMP": 3000, } bld_tally = 0 for bldType in [ universe.getBuilding(bldg).buildingTypeName for bldg in planet.buildingIDs ]: bval = building_values.get(bldType, 50) bld_tally += bval detail.append("%s: %d" % (bldType, bval)) # Add extra score for unlocked techs when we conquer the species tech_tally = 0 value_per_pp = 4 for unlocked_tech in AIDependencies.SPECIES_TECH_UNLOCKS.get( species_name, []): if not tech_is_complete(unlocked_tech): rp_cost = fo.getTech(unlocked_tech).researchCost(empire_id) tech_value = value_per_pp * rp_cost tech_tally += tech_value detail.append("%s: %d" % (unlocked_tech, tech_value)) least_jumps_path, max_jumps = _get_path_from_capital(planet) clear_path = True aistate = get_aistate() system_status = aistate.systemStatus.get(system_id, {}) system_fleet_treat = system_status.get("fleetThreat", 1000) system_monster_threat = system_status.get("monsterThreat", 0) sys_total_threat = system_fleet_treat + system_monster_threat + system_status.get( "planetThreat", 0) max_path_threat = system_fleet_treat mil_ship_rating = MilitaryAI.cur_best_mil_ship_rating() for path_sys_id in least_jumps_path: path_leg_status = aistate.systemStatus.get(path_sys_id, {}) path_leg_threat = path_leg_status.get( "fleetThreat", 1000) + path_leg_status.get("monsterThreat", 0) if path_leg_threat > 0.5 * mil_ship_rating: clear_path = False if path_leg_threat > max_path_threat: max_path_threat = path_leg_threat pop = planet.currentMeterValue(fo.meterType.population) target_pop = planet.currentMeterValue(fo.meterType.targetPopulation) troops = planet.currentMeterValue(fo.meterType.troops) troop_regen = planet.currentMeterValue( fo.meterType.troops) - planet.initialMeterValue(fo.meterType.troops) max_troops = planet.currentMeterValue(fo.meterType.maxTroops) # TODO: refactor troop determination into function for use in mid-mission updates and also consider defender techs max_troops += AIDependencies.TROOPS_PER_POP * (target_pop - pop) this_system = universe.getSystem(system_id) secure_targets = [system_id] + list(this_system.planetIDs) system_secured = False secure_fleets = get_aistate().get_fleet_missions_with_any_mission_types( [MissionType.SECURE, MissionType.MILITARY]) for mission in secure_fleets: secure_fleet_id = mission.fleet.id s_fleet = universe.getFleet(secure_fleet_id) if not s_fleet or s_fleet.systemID != system_id: continue if mission.type in [MissionType.SECURE, MissionType.MILITARY]: target_obj = mission.target.get_object() if target_obj is not None and target_obj.id in secure_targets: system_secured = True break system_secured = system_secured and system_status.get("myFleetRating", 0) debug( "Invasion eval of %s\n" " - maxShields: %.1f\n" " - sysFleetThreat: %.1f\n" " - sysMonsterThreat: %.1f", planet, planet.currentMeterValue(fo.meterType.maxShield), system_fleet_treat, system_monster_threat, ) enemy_val = 0 if planet.owner != -1: # value in taking this away from an enemy enemy_val = 20 * ( planet.currentMeterValue(fo.meterType.targetIndustry) + 2 * planet.currentMeterValue(fo.meterType.targetResearch)) # devalue invasions that would require too much military force preferred_max_portion = MilitaryAI.get_preferred_max_military_portion_for_single_battle( ) total_max_mil_rating = MilitaryAI.get_concentrated_tot_mil_rating() threat_exponent = 2 # TODO: make this a character trait; higher aggression with a lower exponent threat_factor = min( 1, preferred_max_portion * total_max_mil_rating / (sys_total_threat + 0.001))**threat_exponent design_id, _, locs = get_best_ship_info(PriorityType.PRODUCTION_INVASION) if not locs or not universe.getPlanet(locs[0]): # We are in trouble anyway, so just calculate whatever approximation... build_time = 4 planned_troops = troops if system_secured else min( troops + troop_regen * (max_jumps + build_time), max_troops) planned_troops += 0.01 # we must attack with more troops than there are defenders troop_cost = math.ceil((planned_troops + _TROOPS_SAFETY_MARGIN) / 6.0) * 20 * FleetUtilsAI.get_fleet_upkeep() else: loc = locs[0] species_here = universe.getPlanet(loc).speciesName design = fo.getShipDesign(design_id) cost_per_ship = design.productionCost(empire_id, loc) build_time = design.productionTime(empire_id, loc) troops_per_ship = CombatRatingsAI.weight_attack_troops( design.troopCapacity, get_species_tag_grade(species_here, Tags.ATTACKTROOPS)) planned_troops = troops if system_secured else min( troops + troop_regen * (max_jumps + build_time), max_troops) planned_troops += 0.01 # we must attack with more troops than there are defenders ships_needed = math.ceil( (planned_troops + _TROOPS_SAFETY_MARGIN) / float(troops_per_ship)) troop_cost = ships_needed * cost_per_ship # fleet upkeep is already included in query from server # apply some bias to expensive operations normalized_cost = float(troop_cost) / max(fo.getEmpire().productionPoints, 1) normalized_cost = max(1.0, normalized_cost) cost_score = (normalized_cost**2 / 50.0) * troop_cost base_score = colony_base_value + bld_tally + tech_tally + enemy_val - cost_score # If the AI does have enough total military to attack this target, and the target is more than minimally valuable, # don't let the threat_factor discount the adjusted value below MIN_INVASION_SCORE +1, so that if there are no # other targets the AI could still pursue this one. Otherwise, scoring pressure from # MilitaryAI.get_preferred_max_military_portion_for_single_battle might prevent the AI from attacking heavily # defended but still defeatable targets even if it has no softer targets available. if total_max_mil_rating > sys_total_threat and base_score > 2 * MIN_INVASION_SCORE: threat_factor = max(threat_factor, (MIN_INVASION_SCORE + 1) / base_score) planet_score = retaliation_risk_factor(planet.owner) * threat_factor * max( 0, base_score) if clear_path: planet_score *= 1.5 debug( " - planet score: %.2f\n" " - planned troops: %.2f\n" " - projected troop cost: %.1f\n" " - threat factor: %s\n" " - planet detail: %s\n" " - popval: %.1f\n" " - bldval: %s\n" " - enemyval: %s", planet_score, planned_troops, troop_cost, threat_factor, detail, colony_base_value, bld_tally, enemy_val, ) debug(" - system secured: %s" % system_secured) return [planet_score, planned_troops]
def colony_detectable_by_empire(planet_id: int, species_name: Optional[str] = None, empire: Union[int, List[int]] = ALL_EMPIRES, future_stealth_bonus: int = 0, default_result: bool = True) -> bool: """ Predicts if a planet/colony is/will-be detectable by an empire. The passed empire value can be a single empire ID or a list of empire IDs. If passed ALL_empires, will use the list of all empires. When using a list of empires (or when passed ALL_EMPIRES), the present empire is excluded. To check for the present empire the current empire ID must be passed as a simple int. Ignores current ownership of the planet unless the passed empire value is a simple int matching fo.empireID(), because in most cases we are concerned about (i) visibility to us of a planet we do not own, or (ii) visibility by enemies of a planet we own or expect to own at the time we are making the visibility projection for (even if they may own it right now). The only case where current ownership matters is when we are considering whether to abort a colonization mission, which might be for a planet we already own. :param planet_id: required, the planet of concern :param species_name: will override the existing planet species if provided :param empire: empire ID (or list of empire IDs) whose detection ability is of concern :param future_stealth_bonus: can specify a projected future stealth bonus, such as from a stealth tech :param default_result: generally for offensive assessments should be False, for defensive should be True :return: whether the planet is predicted to be detectable """ # The future_stealth_bonus can be used if the AI knows it has researched techs that would grant a stealth bonus to # the planet once it was colonized/captured planet = fo.getUniverse().getPlanet(planet_id) if not planet: error("Couldn't retrieve planet ID %d." % planet_id) return default_result if species_name is None: species_name = planet.speciesName # in case we are checking about aborting a colonization mission if empire == fo.empireID() and planet.ownedBy(empire): return True # could just check stealth meter, but this approach might allow us to plan ahead a bit even if the planet # is temporarily stealth boosted by temporary effects like ion storm planet_stealth = AIDependencies.BASE_PLANET_STEALTH if planet.specials: planet_stealth += max([ AIDependencies.STEALTH_SPECIAL_STRENGTHS.get(_spec, 0) for _spec in planet.specials ]) # TODO: check planet buildings for stealth bonuses # if the planet already has an existing stealth special, then the most common situation is that it would be # overlapping with or superseded by the future_stealth_bonus, not additive with it. planet_stealth = max( planet_stealth, AIDependencies.BASE_PLANET_STEALTH + future_stealth_bonus) species_stealth_mod = AIDependencies.STEALTH_STRENGTHS_BY_SPECIES_TAG.get( get_species_tag_grade(species_name, Tags.STEALTH), 0) total_stealth = planet_stealth + species_stealth_mod if isinstance(empire, int): empire_detection = get_empire_detection(empire) else: empire_detection = get_max_empire_detection(empire) return total_stealth < empire_detection
def _calculate_planet_colonization_rating( planet_id: PlanetId, mission_type: MissionType, species_name: SpeciesName, detail: list, empire_research_list: Sequence, ) -> float: empire = fo.getEmpire() retval = 0 character = get_aistate().character discount_multiplier = character.preferred_discount_multiplier([30.0, 40.0]) species = fo.getSpecies(species_name) species_foci = [] and species and list(species.foci) tag_list = list(species.tags) if species else [] pilot_val = pilot_rating = 0 if species and species.canProduceShips: pilot_val = pilot_rating = rate_piloting_tag(species_name) if pilot_val > best_pilot_rating(): pilot_val *= 2 if pilot_val > 2: retval += discount_multiplier * 5 * pilot_val detail.append("Pilot Val %.1f" % (discount_multiplier * 5 * pilot_val)) if empire.productionPoints < 100: backup_factor = 0.0 else: backup_factor = min(1.0, (empire.productionPoints / 200.0)**2) universe = fo.getUniverse() capital_id = PlanetUtilsAI.get_capital() homeworld = universe.getPlanet(capital_id) planet = universe.getPlanet(planet_id) prospective_invasion_targets = [ pid for pid, pscore, trp in AIstate.invasionTargets[:PriorityAI.allotted_invasion_targets()] if pscore > InvasionAI.MIN_INVASION_SCORE ] if species_name != planet.speciesName and planet.speciesName and mission_type != MissionType.INVASION: return 0 this_sysid = planet.systemID if homeworld: home_system_id = homeworld.systemID eval_system_id = this_sysid if home_system_id != INVALID_ID and eval_system_id != INVALID_ID: least_jumps = universe.jumpDistance(home_system_id, eval_system_id) if least_jumps == -1: # indicates no known path return 0.0 if planet is None: vis_map = universe.getVisibilityTurnsMap(planet_id, empire.empireID) debug("Planet %d object not available; visMap: %s" % (planet_id, vis_map)) return 0 # only count existing presence if not target planet # TODO: consider neighboring sytems for smaller contribution, and bigger contributions for # local colonies versus local outposts locally_owned_planets = [ lpid for lpid in get_owned_planets_in_system(this_sysid) if lpid != planet_id ] planets_with_species = get_inhabited_planets() locally_owned_pop_ctrs = [ lpid for lpid in locally_owned_planets if lpid in planets_with_species ] # triple count pop_ctrs existing_presence = len( locally_owned_planets) + 2 * len(locally_owned_pop_ctrs) system = universe.getSystem(this_sysid) sys_supply = get_system_supply(this_sysid) planet_supply = AIDependencies.supply_by_size.get(planet.size, 0) bld_types = set( universe.getBuilding(bldg).buildingTypeName for bldg in planet.buildingIDs).intersection( AIDependencies.building_supply) planet_supply += sum( AIDependencies.building_supply[bld_type].get(int(psize), 0) for psize in [-1, planet.size] for bld_type in bld_types) supply_specials = set(planet.specials).union(system.specials).intersection( AIDependencies.SUPPLY_MOD_SPECIALS) planet_supply += sum( AIDependencies.SUPPLY_MOD_SPECIALS[_special].get(int(psize), 0) for _special in supply_specials for psize in [-1, planet.size]) ind_tag_mod = AIDependencies.SPECIES_INDUSTRY_MODIFIER.get( get_species_tag_grade(species_name, Tags.INDUSTRY), 1.0) res_tag_mod = AIDependencies.SPECIES_RESEARCH_MODIFIER.get( get_species_tag_grade(species_name, Tags.RESEARCH), 1.0) if species: supply_tag_mod = AIDependencies.SPECIES_SUPPLY_MODIFIER.get( get_species_tag_grade(species_name, Tags.SUPPLY), 1) else: supply_tag_mod = 0 # determine the potential supply provided by owning this planet, and if the planet is currently populated by # the evaluated species, then save this supply value in a cache. # The system supply value can be negative (indicates the respective number of starlane jumps to the closest supplied # system). So if this total planet supply value is non-negative, then the planet would be supply connected (to at # least a portion of the existing empire) if the planet becomes owned by the AI. planet_supply += supply_tag_mod planet_supply = max(planet_supply, 0) # planets can't have negative supply if planet.speciesName == species_name: update_planet_supply(planet_id, planet_supply + sys_supply) threat_factor = _determine_colony_threat_factor(planet_id, species_name, existing_presence) sys_partial_vis_turn = get_partial_visibility_turn(this_sysid) planet_partial_vis_turn = get_partial_visibility_turn(planet_id) if planet_partial_vis_turn < sys_partial_vis_turn: # last time we had partial vis of the system, the planet was stealthed to us # print "Colonization AI couldn't get current info on planet id %d (was stealthed at last sighting)" % planet_id return 0 # TODO: track detection strength, order new scouting when it goes up star_bonus = 0 colony_star_bonus = 0 research_bonus = 0 growth_val = 0 fixed_ind = 0 fixed_res = 0 if system: already_got_this_one = this_sysid in get_owned_planets() # TODO: Should probably consider pilot rating also for Phototropic species if "PHOTOTROPHIC" not in tag_list and pilot_rating >= best_pilot_rating( ): if system.starType == fo.starType.red and tech_is_complete( "LRN_STELLAR_TOMOGRAPHY"): star_bonus += 40 * discount_multiplier # can be used for artif'l black hole and solar hull detail.append( "Red Star for Art Black Hole for solar hull %.1f" % (40 * discount_multiplier)) elif system.starType == fo.starType.blackHole and tech_is_complete( "SHP_FRC_ENRG_COMP"): star_bonus += 100 * discount_multiplier # can be used for solar hull detail.append("Black Hole for solar hull %.1f" % (100 * discount_multiplier)) if tech_is_complete("PRO_SOL_ORB_GEN" ) or "PRO_SOL_ORB_GEN" in empire_research_list[:5]: if system.starType in [fo.starType.blue, fo.starType.white]: if not has_claimed_star(fo.starType.blue, fo.starType.white): star_bonus += 20 * discount_multiplier detail.append("PRO_SOL_ORB_GEN BW %.1f" % (20 * discount_multiplier)) elif not already_got_this_one: # still has extra value as an alternate location for solar generators star_bonus += 10 * discount_multiplier * backup_factor detail.append("PRO_SOL_ORB_GEN BW Backup Location %.1f" % (10 * discount_multiplier * backup_factor)) elif fo.currentTurn() > 100: # lock up this whole system pass # starBonus += 5 # TODO: how much? # detail.append("PRO_SOL_ORB_GEN BW LockingDownSystem %.1f"%5) if system.starType in [fo.starType.yellow, fo.starType.orange]: if not has_claimed_star(fo.starType.blue, fo.starType.white, fo.starType.yellow, fo.starType.orange): star_bonus += 10 * discount_multiplier detail.append("PRO_SOL_ORB_GEN YO %.1f" % (10 * discount_multiplier)) else: pass # starBonus +=2 # still has extra value as an alternate location for solar generators # detail.append("PRO_SOL_ORB_GEN YO Backup %.1f" % 2) if system.starType in [fo.starType.blackHole ] and fo.currentTurn() > 100: if not already_got_this_one: # whether have tech yet or not, assign some base value star_bonus += 10 * discount_multiplier * backup_factor detail.append("Black Hole %.1f" % (10 * discount_multiplier * backup_factor)) else: star_bonus += 5 * discount_multiplier * backup_factor detail.append("Black Hole Backup %.1f" % (5 * discount_multiplier * backup_factor)) if tech_is_complete(AIDependencies.PRO_SOL_ORB_GEN ): # start valuing as soon as PRO_SOL_ORB_GEN done if system.starType == fo.starType.blackHole: # pretty rare planets, good for generator this_val = 0.5 * max(population_with_industry_focus(), 20) * discount_multiplier if not has_claimed_star(fo.starType.blackHole): star_bonus += this_val detail.append("PRO_SINGULAR_GEN %.1f" % this_val) elif not is_system_star_claimed(system): # still has extra value as an alternate location for generators & for blocking enemies generators star_bonus += this_val * backup_factor detail.append("PRO_SINGULAR_GEN Backup %.1f" % (this_val * backup_factor)) elif system.starType == fo.starType.red and not has_claimed_star( fo.starType.blackHole): rfactor = (1.0 + count_claimed_stars(fo.starType.red))**-2 star_bonus += 40 * discount_multiplier * backup_factor * rfactor # can be used for artif'l black hole detail.append( "Red Star for Art Black Hole %.1f" % (40 * discount_multiplier * backup_factor * rfactor)) if tech_is_complete( "PRO_NEUTRONIUM_EXTRACTION" ) or "PRO_NEUTRONIUM_EXTRACTION" in empire_research_list[:8]: if system.starType in [fo.starType.neutron]: if not has_claimed_star(fo.starType.neutron): star_bonus += 80 * discount_multiplier # pretty rare planets, good for armor detail.append("PRO_NEUTRONIUM_EXTRACTION %.1f" % (80 * discount_multiplier)) else: # still has extra value as an alternate location for generators & for bnlocking enemies generators star_bonus += 20 * discount_multiplier * backup_factor detail.append("PRO_NEUTRONIUM_EXTRACTION Backup %.1f" % (20 * discount_multiplier * backup_factor)) if tech_is_complete( "SHP_ENRG_BOUND_MAN" ) or "SHP_ENRG_BOUND_MAN" in empire_research_list[:6]: # TODO: base this on pilot val, and also consider red stars if system.starType in [fo.starType.blackHole, fo.starType.blue]: init_val = 100 * discount_multiplier * (pilot_val or 1) if not has_claimed_star(fo.starType.blackHole, fo.starType.blue): colony_star_bonus += init_val # pretty rare planets, good for energy shipyards detail.append("SHP_ENRG_BOUND_MAN %.1f" % init_val) elif not is_system_star_claimed(system): # still has extra value as an alternate location for energy shipyard colony_star_bonus += 0.5 * init_val * backup_factor detail.append("SHP_ENRG_BOUND_MAN Backup %.1f" % (0.5 * init_val * backup_factor)) retval += star_bonus planet_specials = list(planet.specials) if "ECCENTRIC_ORBIT_SPECIAL" in planet.specials: fixed_res += discount_multiplier * 6 detail.append("ECCENTRIC_ORBIT_SPECIAL %.1f" % (discount_multiplier * 6)) if mission_type == MissionType.OUTPOST or ( mission_type == MissionType.INVASION and not species_name): if "ANCIENT_RUINS_SPECIAL" in planet.specials: # TODO: add value for depleted ancient ruins retval += discount_multiplier * 30 detail.append("Undepleted Ruins %.1f" % discount_multiplier * 30) for special in planet_specials: if "_NEST_" in special: nest_val = get_nest_rating( special, 5.0 ) * discount_multiplier # get an outpost on the nest quick retval += nest_val detail.append("%s %.1f" % (special, nest_val)) elif special == "FORTRESS_SPECIAL": fort_val = 10 * discount_multiplier retval += fort_val detail.append("%s %.1f" % (special, fort_val)) elif special == "HONEYCOMB_SPECIAL": honey_val = 0.3 * (AIDependencies.HONEYCOMB_IND_MULTIPLIER * AIDependencies.INDUSTRY_PER_POP * population_with_industry_focus() * discount_multiplier) retval += honey_val detail.append("%s %.1f" % (special, honey_val)) if planet.size == fo.planetSize.asteroids: ast_val = 0 if system: for pid in system.planetIDs: other_planet = universe.getPlanet(pid) if other_planet.size == fo.planetSize.asteroids: if pid == planet_id: continue elif pid < planet_id and planet.unowned: ast_val = 0 break elif other_planet.speciesName: if other_planet.owner == empire.empireID: ownership_factor = 1.0 elif pid in prospective_invasion_targets: ownership_factor = character.secondary_valuation_factor_for_invasion_targets( ) else: ownership_factor = 0.0 ast_val += ownership_factor * _base_asteroid_mining_val( ) * discount_multiplier retval += ast_val if ast_val > 0: detail.append("AsteroidMining %.1f" % ast_val) ast_val = 0 if tech_is_complete("SHP_ASTEROID_HULLS"): per_ast = 20 elif tech_is_complete("CON_ORBITAL_CON"): per_ast = 5 else: per_ast = 0.1 if system: for pid in system.planetIDs: other_planet = universe.getPlanet(pid) if other_planet.size == fo.planetSize.asteroids: if pid == planet_id: continue elif pid < planet_id and planet.unowned: ast_val = 0 break elif other_planet.speciesName: other_species = fo.getSpecies(other_planet.speciesName) if other_species and other_species.canProduceShips: if other_planet.owner == empire.empireID: ownership_factor = 1.0 elif pid in prospective_invasion_targets: ownership_factor = character.secondary_valuation_factor_for_invasion_targets( ) else: ownership_factor = 0.0 ast_val += ownership_factor * per_ast * discount_multiplier retval += ast_val if ast_val > 0: detail.append("AsteroidShipBuilding %.1f" % ast_val) # We will assume that if any GG in the system is populated, they all will wind up populated; cannot then hope # to build a GGG on a non-populated GG populated_gg_factor = 1.0 per_gg = 0 if planet.size == fo.planetSize.gasGiant: # TODO: Given current industry calc approach, consider bringing this max val down to actual max val of 10 if tech_is_complete("PRO_ORBITAL_GEN"): per_gg = 20 elif tech_is_complete("CON_ORBITAL_CON"): per_gg = 10 if species_name: populated_gg_factor = 0.5 else: per_gg = 5 if system: gg_list = [] orb_gen_val = 0 gg_detail = [] for pid in system.planetIDs: other_planet = universe.getPlanet(pid) if other_planet.size == fo.planetSize.gasGiant: gg_list.append(pid) if other_planet.speciesName: populated_gg_factor = 0.5 if (pid != planet_id and other_planet.owner == empire.empireID and FocusType.FOCUS_INDUSTRY in list(other_planet.availableFoci) + [other_planet.focus]): orb_gen_val += per_gg * discount_multiplier # Note, this reported value may not take into account a later adjustment from a populated gg gg_detail.append("GGG for %s %.1f" % (other_planet.name, discount_multiplier * per_gg * populated_gg_factor)) if planet_id in sorted(gg_list)[:1]: retval += orb_gen_val * populated_gg_factor detail.extend(gg_detail) else: detail.append("Won't GGG") if existing_presence: detail.append("preexisting system colony") retval = (retval + existing_presence * _get_defense_value(species_name)) * 1.5 # Fixme - sys_supply is always <= 0 leading to incorrect supply bonus score supply_val = 0 if sys_supply < 0: if sys_supply + planet_supply >= 0: supply_val += 30 * (planet_supply - max(-3, sys_supply)) else: retval += 30 * (planet_supply + sys_supply) # a penalty elif planet_supply > sys_supply and ( sys_supply < 2): # TODO: check min neighbor supply supply_val += 25 * (planet_supply - sys_supply) detail.append("sys_supply: %d, planet_supply: %d, supply_val: %.0f" % (sys_supply, planet_supply, supply_val)) retval += supply_val if threat_factor < 1.0: threat_factor = _revise_threat_factor(threat_factor, retval, this_sysid, MINIMUM_COLONY_SCORE) retval *= threat_factor detail.append("threat reducing value by %3d %%" % (100 * (1 - threat_factor))) return int(retval) else: # colonization mission if not species: return 0 supply_val = 0 if "ANCIENT_RUINS_SPECIAL" in planet.specials: retval += discount_multiplier * 50 detail.append("Undepleted Ruins %.1f" % discount_multiplier * 50) if "HONEYCOMB_SPECIAL" in planet.specials: honey_val = (AIDependencies.HONEYCOMB_IND_MULTIPLIER * AIDependencies.INDUSTRY_PER_POP * population_with_industry_focus() * discount_multiplier) if FocusType.FOCUS_INDUSTRY not in species_foci: honey_val *= -0.3 # discourage settlement by colonizers not able to use Industry Focus retval += honey_val detail.append("%s %.1f" % ("HONEYCOMB_SPECIAL", honey_val)) # Fixme - sys_supply is always <= 0 leading to incorrect supply bonus score if sys_supply <= 0: if sys_supply + planet_supply >= 0: supply_val = 40 * (planet_supply - max(-3, sys_supply)) else: supply_val = 200 * (planet_supply + sys_supply) # a penalty if species_name == "SP_SLY": # Sly are essentially stuck with lousy supply, so don't penalize for that supply_val = 0 elif planet_supply > sys_supply == 1: # TODO: check min neighbor supply supply_val = 20 * (planet_supply - sys_supply) detail.append("sys_supply: %d, planet_supply: %d, supply_val: %.0f" % (sys_supply, planet_supply, supply_val)) # if AITags != "": # print "Species %s has AITags %s"%(specName, AITags) retval += fixed_res retval += colony_star_bonus asteroid_bonus = 0 gas_giant_bonus = 0 flat_industry = 0 mining_bonus = 0 per_ggg = 10 asteroid_factor = 0.0 gg_factor = 0.0 ast_shipyard_name = "" if system and FocusType.FOCUS_INDUSTRY in species.foci: for pid in system.planetIDs: if pid == planet_id: continue p2 = universe.getPlanet(pid) if p2: if p2.size == fo.planetSize.asteroids: this_factor = 0.0 if p2.owner == empire.empireID: this_factor = 1.0 elif p2.unowned: this_factor = 0.5 elif pid in prospective_invasion_targets: this_factor = character.secondary_valuation_factor_for_invasion_targets( ) if this_factor > asteroid_factor: asteroid_factor = this_factor ast_shipyard_name = p2.name if p2.size == fo.planetSize.gasGiant: if p2.owner == empire.empireID: gg_factor = max(gg_factor, 1.0) elif p2.unowned: gg_factor = max(gg_factor, 0.5) elif pid in prospective_invasion_targets: gg_factor = max( gg_factor, character. secondary_valuation_factor_for_invasion_targets( )) if asteroid_factor > 0.0: if tech_is_complete( "PRO_MICROGRAV_MAN" ) or "PRO_MICROGRAV_MAN" in empire_research_list[:10]: flat_industry += 2 * asteroid_factor # will go into detailed industry projection detail.append("Asteroid mining ~ %.1f" % (5 * asteroid_factor * discount_multiplier)) if tech_is_complete( "SHP_ASTEROID_HULLS" ) or "SHP_ASTEROID_HULLS" in empire_research_list[:11]: if species and species.canProduceShips: asteroid_bonus = 30 * discount_multiplier * pilot_val detail.append("Asteroid ShipBuilding from %s %.1f" % (ast_shipyard_name, discount_multiplier * 30 * pilot_val)) if gg_factor > 0.0: if tech_is_complete( "PRO_ORBITAL_GEN" ) or "PRO_ORBITAL_GEN" in empire_research_list[:5]: flat_industry += per_ggg * gg_factor # will go into detailed industry projection detail.append("GGG ~ %.1f" % (per_ggg * gg_factor * discount_multiplier)) # calculate the maximum population of the species on that planet. if planet.speciesName not in AIDependencies.SPECIES_FIXED_POPULATION: max_pop_size = calc_max_pop(planet, species, detail) else: max_pop_size = AIDependencies.SPECIES_FIXED_POPULATION[ planet.speciesName] detail.append("Fixed max population of %.2f" % max_pop_size) if max_pop_size <= 0: detail.append( "Non-positive population projection for species '%s', so no colonization value" % (species and species.name)) return 0 for special in [ "MINERALS_SPECIAL", "CRYSTALS_SPECIAL", "ELERIUM_SPECIAL" ]: if special in planet_specials: mining_bonus += 1 has_blackhole = has_claimed_star(fo.starType.blackHole) ind_tech_map_flat = AIDependencies.INDUSTRY_EFFECTS_FLAT_NOT_MODIFIED_BY_SPECIES ind_tech_map_before_species_mod = AIDependencies.INDUSTRY_EFFECTS_PER_POP_MODIFIED_BY_SPECIES ind_tech_map_after_species_mod = AIDependencies.INDUSTRY_EFFECTS_PER_POP_NOT_MODIFIED_BY_SPECIES ind_mult = 1 for tech in ind_tech_map_before_species_mod: if tech_is_complete(tech) and ( tech != AIDependencies.PRO_SINGULAR_GEN or has_blackhole): ind_mult += ind_tech_map_before_species_mod[tech] ind_mult = ind_mult * max( ind_tag_mod, 0.5 * (ind_tag_mod + res_tag_mod)) # TODO: report an actual calc for research value for tech in ind_tech_map_after_species_mod: if tech_is_complete(tech) and ( tech != AIDependencies.PRO_SINGULAR_GEN or has_blackhole): ind_mult += ind_tech_map_after_species_mod[tech] max_ind_factor = 0 for tech in ind_tech_map_flat: if tech_is_complete(tech): fixed_ind += discount_multiplier * ind_tech_map_flat[tech] if FocusType.FOCUS_INDUSTRY in species.foci: if "TIDAL_LOCK_SPECIAL" in planet.specials: ind_mult += 1 max_ind_factor += AIDependencies.INDUSTRY_PER_POP * mining_bonus max_ind_factor += AIDependencies.INDUSTRY_PER_POP * ind_mult cur_pop = 1.0 # assume an initial colonization value if planet.speciesName != "": cur_pop = planet.currentMeterValue(fo.meterType.population) elif tech_is_complete("GRO_LIFECYCLE_MAN"): cur_pop = 3.0 cur_industry = planet.currentMeterValue(fo.meterType.industry) ind_val = _project_ind_val(cur_pop, max_pop_size, cur_industry, max_ind_factor, flat_industry, discount_multiplier) detail.append("ind_val %.1f" % ind_val) # used to give preference to closest worlds for special in [ spec for spec in planet_specials if spec in AIDependencies.metabolismBoosts ]: # TODO: also consider potential future benefit re currently unpopulated planets gbonus = (discount_multiplier * AIDependencies.INDUSTRY_PER_POP * ind_mult * empire_metabolisms.get( AIDependencies.metabolismBoosts[special], 0) ) # due to growth applicability to other planets growth_val += gbonus detail.append("Bonus for %s: %.1f" % (special, gbonus)) if FocusType.FOCUS_RESEARCH in species.foci: research_bonus += discount_multiplier * 2 * AIDependencies.RESEARCH_PER_POP * max_pop_size if "ANCIENT_RUINS_SPECIAL" in planet.specials or "ANCIENT_RUINS_DEPLETED_SPECIAL" in planet.specials: research_bonus += discount_multiplier * 2 * AIDependencies.RESEARCH_PER_POP * max_pop_size * 5 detail.append("Ruins Research") if "TEMPORAL_ANOMALY_SPECIAL" in planet.specials: research_bonus += discount_multiplier * 2 * AIDependencies.RESEARCH_PER_POP * max_pop_size * 25 detail.append("Temporal Anomaly Research") if AIDependencies.COMPUTRONIUM_SPECIAL in planet.specials: comp_bonus = (0.5 * AIDependencies.TECH_COST_MULTIPLIER * AIDependencies.RESEARCH_PER_POP * AIDependencies.COMPUTRONIUM_RES_MULTIPLIER * population_with_research_focus() * discount_multiplier) if have_computronium(): comp_bonus *= backup_factor research_bonus += comp_bonus detail.append(AIDependencies.COMPUTRONIUM_SPECIAL) retval += (max(ind_val + asteroid_bonus + gas_giant_bonus, research_bonus, growth_val) + fixed_ind + fixed_res + supply_val) if existing_presence: detail.append("preexisting system colony") retval = (retval + existing_presence * _get_defense_value(species_name)) * 2 if threat_factor < 1.0: threat_factor = _revise_threat_factor(threat_factor, retval, this_sysid, MINIMUM_COLONY_SCORE) retval *= threat_factor detail.append("threat reducing value by %3d %%" % (100 * (1 - threat_factor))) return retval
def rate_piloting_tag(species_name: str) -> float: weapon_grade_tag = get_species_tag_grade(species_name, Tags.WEAPONS) return _pilot_tags_rating.get(weapon_grade_tag, 1.0)
def calc_max_pop(planet, species, detail): planet_size = planet.habitableSize planet_env = species.getPlanetEnvironment(planet.type) if planet_env == fo.planetEnvironment.uninhabitable: detail.append("Uninhabitable.") return 0 for bldg_id in planet.buildingIDs: building = fo.getUniverse().getBuilding(bldg_id) if not building: continue if building.buildingTypeName == "BLD_GATEWAY_VOID": detail.append("Gateway to the void: Uninhabitable.") return 0 tag_list = list(species.tags) if species else [] pop_tag_mod = AIDependencies.SPECIES_POPULATION_MODIFIER.get( get_species_tag_grade(species.name, Tags.POPULATION), 1.0) if planet.type == fo.planetType.gasGiant and "GASEOUS" in tag_list: gaseous_adjustment = AIDependencies.GASEOUS_POP_FACTOR detail.append("GASEOUS adjustment: %.2f" % gaseous_adjustment) else: gaseous_adjustment = 1.0 if planet.type != fo.planetType.asteroids and "MINDLESS_SPECIES" in tag_list: mindless_adjustment = AIDependencies.MINDLESS_POP_FACTOR detail.append("MINDLESS adjustment: %.2f" % mindless_adjustment) else: mindless_adjustment = 1.0 planet_specials = set(planet.specials) base_pop_modified_by_species = 0 base_pop_not_modified_by_species = 0 pop_const_mod = 0 # first, account for the environment environment_mod = POP_SIZE_MOD_MAP_MODIFIED_BY_SPECIES[ "environment_bonus"][planet_env] detail.append("Base environment: %d" % environment_mod) base_pop_modified_by_species += environment_mod # find all applicable modifiers for tech in POP_SIZE_MOD_MAP_MODIFIED_BY_SPECIES: if tech != "environment_bonus" and tech_is_complete(tech): base_pop_modified_by_species += POP_SIZE_MOD_MAP_MODIFIED_BY_SPECIES[ tech][planet_env] detail.append( "%s_PSM_early(%d)" % (tech, POP_SIZE_MOD_MAP_MODIFIED_BY_SPECIES[tech][planet_env])) for tech in POP_SIZE_MOD_MAP_NOT_MODIFIED_BY_SPECIES: if tech_is_complete(tech): base_pop_not_modified_by_species += POP_SIZE_MOD_MAP_NOT_MODIFIED_BY_SPECIES[ tech][planet_env] detail.append( "%s_PSM_late(%d)" % (tech, POP_SIZE_MOD_MAP_NOT_MODIFIED_BY_SPECIES[tech][planet_env])) for tech in POP_CONST_MOD_MAP: if tech_is_complete(tech): pop_const_mod += POP_CONST_MOD_MAP[tech][planet_env] detail.append("%s_PCM(%d)" % (tech, POP_CONST_MOD_MAP[tech][planet_env])) for _special in planet_specials.intersection( AIDependencies.POP_FIXED_MOD_SPECIALS): this_mod = AIDependencies.POP_FIXED_MOD_SPECIALS[_special] detail.append("%s_PCM(%d)" % (_special, this_mod)) pop_const_mod += this_mod for _special in planet_specials.intersection( AIDependencies.POP_PROPORTIONAL_MOD_SPECIALS): this_mod = AIDependencies.POP_PROPORTIONAL_MOD_SPECIALS[_special] detail.append("%s (maxPop%+.1f)" % (_special, this_mod)) base_pop_not_modified_by_species += this_mod # exobots and sly can't ever get to good environ so no gaia bonus, for others we'll assume they'll get there if "GAIA_SPECIAL" in planet_specials and species.name != "SP_EXOBOT" and species.name != "SP_SLY": base_pop_not_modified_by_species += 3 detail.append("Gaia_PSM_late(3)") if "SELF_SUSTAINING" in tag_list: if planet_env == fo.planetEnvironment.good: base_pop_not_modified_by_species += 3 detail.append("SelfSustaining_PSM_late(3)") applicable_boosts = set() for this_tag in [ tag for tag in tag_list if tag in AIDependencies.metabolismBoostMap ]: metab_boosts = AIDependencies.metabolismBoostMap.get(this_tag, []) for key in active_growth_specials.keys(): if len(active_growth_specials[key]) > 0 and key in metab_boosts: applicable_boosts.add(key) detail.append("%s boost active" % key) for boost in metab_boosts: if boost in planet_specials: applicable_boosts.add(boost) detail.append("%s boost present" % boost) n_boosts = len(applicable_boosts) if n_boosts: base_pop_not_modified_by_species += n_boosts detail.append("boosts_PSM(%d from %s)" % (n_boosts, applicable_boosts)) if planet.id in species.homeworlds: detail.append("Homeworld (2)") base_pop_not_modified_by_species += 2 if AIDependencies.TAG_LIGHT_SENSITIVE in tag_list: star_type = fo.getUniverse().getSystem(planet.systemID).starType star_pop_mod = AIDependencies.POP_MOD_LIGHTSENSITIVE_STAR_MAP.get( star_type, 0) base_pop_not_modified_by_species += star_pop_mod detail.append("Lightsensitive Star Bonus_PSM_late(%.1f)" % star_pop_mod) def max_pop_size(): species_effect = (pop_tag_mod - 1) * abs(base_pop_modified_by_species) gaseous_effect = (gaseous_adjustment - 1) * abs(base_pop_modified_by_species) mindless_effect = (mindless_adjustment - 1) * abs(base_pop_modified_by_species) base_pop = (base_pop_not_modified_by_species + base_pop_modified_by_species + species_effect + gaseous_effect + mindless_effect) return planet_size * base_pop + pop_const_mod target_pop = max_pop_size() if "PHOTOTROPHIC" in tag_list and target_pop > 0: star_type = fo.getUniverse().getSystem(planet.systemID).starType star_pop_mod = AIDependencies.POP_MOD_PHOTOTROPHIC_STAR_MAP.get( star_type, 0) base_pop_not_modified_by_species += star_pop_mod detail.append("Phototropic Star Bonus_PSM_late(%0.1f)" % star_pop_mod) target_pop = max_pop_size() detail.append( "max_pop = base + size*[psm_early + species_mod*abs(psm_early) + psm_late]" ) detail.append(" = %.2f + %d * [%.2f + %.2f*abs(%.2f) + %.2f]" % ( pop_const_mod, planet_size, base_pop_modified_by_species, (pop_tag_mod + gaseous_adjustment - 2), base_pop_modified_by_species, base_pop_not_modified_by_species, )) detail.append(" = %.2f" % target_pop) return target_pop