def dislike_factor() -> float: """Returns multiplier for dislike effects.""" # See happiness.macros has_liberty = fo.getEmpire().policyAdopted("PLC_LIBERTY") # conformance not used yet return fo.getNamedValue( "PLC_LIBERTY_DISLIKE_FACTOR") if has_liberty else 1.0
def _evaluate_policies(species: fo.species) -> float: empire = fo.getEmpire() like_value = AIDependencies.STABILITY_PER_LIKED_FOCUS dislike_value = like_value * dislike_factor() result = sum(like_value for p in empire.adoptedPolicies if p in species.likes) result -= sum(dislike_value for p in empire.adoptedPolicies if p in species.dislikes) if PolicyAI.bureaucracy in empire.adoptedPolicies: result += fo.getNamedValue("PLC_BUREAUCRACY_STABILITY_FLAT") # TBD: add conformance, indoctrination, etc. when the AI learns to use them return result
def _rate_diversity(self) -> float: """Rate diversity.""" if diversity not in self._empire.availablePolicies: return 0.0 # diversity affects stability, but also gives a bonus to research-focused planets and a little influence, diversity_value = len(get_empire_planets_by_species() ) - fo.getNamedValue("PLC_DIVERSITY_THRESHOLD") diversity_scaling = fo.getNamedValue("PLC_DIVERSITY_SCALING") # Research bonus goes to research-focused planets only. With priority there are usually none. research_priority = self._aistate.get_priority( PriorityType.RESOURCE_RESEARCH) research_bonus = max(0, research_priority - 20) # + 4 for global influence global_influence_bonus = 4 rating = self._rate_opinion( diversity) + diversity_scaling * diversity_value * ( self._num_populated + global_influence_bonus + research_bonus) debug( f"_rate_diversity: rating={rating}. diversity_value={diversity_value}, " f"research priority={research_priority}") return rating
def _evaluate_xenophobia(planet, species) -> float: if AIDependencies.Tags.XENOPHOBIC not in species.tags: return 0.0 colonies = get_colonized_planets() relevant_systems = within_n_jumps(planet.systemID, 5) & set( colonies.keys()) result = 0.0 universe = fo.getUniverse() value = fo.getNamedValue("XENOPHOBIC_SELF_TARGET_HAPPINESS_COUNT") for sys_id in relevant_systems: for pid in colonies[sys_id]: planet_species = universe.getPlanet(pid).speciesName if planet_species not in ("SP_EXOBOT", species.name, ""): result += value # value is negative return result
def get_named_real(name: str) -> float: """ Returns a NamedReal from FOCS. If the value does not exist, reports an error and returns 1.0. Note that we do not raise and exception so that the AI can continue, as good as it can, with outdated information. This is also why we return 1, returning 0 could cause followup errors if the value is used as divisor. """ value = fo.getNamedValue(name) if value is None: error(f"Requested NamedReal {name}, which doesn't exist!") value = 1.0 elif not isinstance(value, float): error(f"Requested value {name} of type float got {type(value)}!") value = 1.0 return value
def _rate_artisan_planet(self, pid: PlanetId, species_name: SpeciesName) -> float: focus_bonus = fo.getNamedValue("ARTISANS_INFLUENCE_FLAT_FOCUS") focus_minimum = fo.getNamedValue("ARTISANS_MIN_STABILITY_FOCUS") species_focus_bonus = focus_bonus * get_species_tag_value( species_name, Tags.INFLUENCE) planet = self._universe.getPlanet(pid) stability = planet.currentMeterValue(fo.meterType.targetHappiness) # First check whether the planet would currently get the focus bonus. if planet.focus == FocusType.FOCUS_INFLUENCE: return 3 * species_focus_bonus if stability >= focus_minimum else 0.0 # Planet does not have influence focus... # Check for the non-focus bonus. Since we would get this "for free", rate it higher non_focus_bonus = fo.getNamedValue("ARTISANS_INFLUENCE_FLAT_NO_FOCUS") non_focus_minimum = fo.getNamedValue("ARTISANS_MIN_STABILITY_NO_FOCUS") rating = 0.0 if stability >= non_focus_minimum: rating += 4 * non_focus_bonus # Check whether this planet would get the focus bonus, if we'd switch it to influence. if PlanetUtilsAI.stability_with_focus( planet, FocusType.FOCUS_INFLUENCE) >= focus_minimum: rating += species_focus_bonus return rating
def assess_protection_focus(pinfo, priority): """Return True if planet should use Protection Focus.""" this_planet = pinfo.planet # this is unrelated to military threats stability_bonus = (pinfo.current_focus == PROTECTION ) * fo.getNamedValue("PROTECION_FOCUS_STABILITY_BONUS") # industry and research produce nothing below 0 threshold = -1 * (pinfo.current_focus not in (INDUSTRY, RESEARCH)) # Negative IP lowers stability. Trying to counter this by setting planets to Protection just makes it worse! ip = fo.getEmpire().resourceAvailable(fo.resourceType.influence) if ip >= 0 and this_planet.currentMeterValue( fo.meterType.targetHappiness) < threshold + stability_bonus: debug("Advising Protection Focus at %s to avoid rebellion", this_planet) return True aistate = get_aistate() sys_status = aistate.systemStatus.get(this_planet.systemID, {}) threat_from_supply = ( 0.25 * aistate.empire_standard_enemy_rating * min(2, len(sys_status.get("enemies_nearly_supplied", [])))) debug("%s has regional+supply threat of %.1f", this_planet, threat_from_supply) regional_threat = sys_status.get("regional_threat", 0) + threat_from_supply if not regional_threat: # no need for protection if pinfo.current_focus == PROTECTION: debug( "Advising dropping Protection Focus at %s due to no regional threat", this_planet) return False cur_prod_val = weighted_sum_output((pinfo.current_output, priority)) target_prod_val = max( map( weighted_sum_output, [ (pinfo.possible_output[INDUSTRY], priority), (pinfo.possible_output[RESEARCH], priority), (pinfo.possible_output[INFLUENCE], priority), ], )) prot_prod_val = weighted_sum_output( (pinfo.possible_output[PROTECTION], priority)) local_production_diff = 0.5 * cur_prod_val + 0.5 * target_prod_val - prot_prod_val fleet_threat = sys_status.get("fleetThreat", 0) # TODO: relax the below rejection once the overall determination of PFocus is better tuned # priorities have a magnitude of 50 if not fleet_threat and local_production_diff > 200: if pinfo.current_focus == PROTECTION: debug( "Advising dropping Protection Focus at %s due to excessive productivity loss", this_planet) return False local_p_defenses = sys_status.get("mydefenses", {}).get("overall", 0) # TODO have adjusted_p_defenses take other in-system planets into account adjusted_p_defenses = local_p_defenses * ( 1.0 if pinfo.current_focus != PROTECTION else 0.5) local_fleet_rating = sys_status.get("myFleetRating", 0) combined_local_defenses = sys_status.get("all_local_defenses", 0) my_neighbor_rating = sys_status.get("my_neighbor_rating", 0) neighbor_threat = sys_status.get("neighborThreat", 0) safety_factor = 1.2 if pinfo.current_focus == PROTECTION else 0.5 cur_shield = this_planet.initialMeterValue(fo.meterType.shield) max_shield = this_planet.initialMeterValue(fo.meterType.maxShield) cur_troops = this_planet.initialMeterValue(fo.meterType.troops) max_troops = this_planet.initialMeterValue(fo.meterType.maxTroops) cur_defense = this_planet.initialMeterValue(fo.meterType.defense) max_defense = this_planet.initialMeterValue(fo.meterType.maxDefense) def_meter_pairs = [(cur_troops, max_troops), (cur_shield, max_shield), (cur_defense, max_defense)] use_protection = True reason = "" if fleet_threat and ( # i.e., an enemy is sitting on us pinfo.current_focus != PROTECTION or # too late to start protection TODO: but maybe regen worth it # protection focus only useful here if it maintains an elevated level all([ AIDependencies.PROT_FOCUS_MULTIPLIER * a <= b for a, b in def_meter_pairs ])): use_protection = False reason = "A" elif ((pinfo.current_focus != PROTECTION and cur_shield < max_shield - 2 and not tech_is_complete(AIDependencies.PLANET_BARRIER_I_TECH)) and (cur_defense < max_defense - 2 and not tech_is_complete(AIDependencies.DEFENSE_REGEN_1_TECH)) and (cur_troops < max_troops - 2)): use_protection = False reason = "B1" elif ( (pinfo.current_focus == PROTECTION and cur_shield * AIDependencies.PROT_FOCUS_MULTIPLIER < max_shield - 2 and not tech_is_complete(AIDependencies.PLANET_BARRIER_I_TECH)) and (cur_defense * AIDependencies.PROT_FOCUS_MULTIPLIER < max_defense - 2 and not tech_is_complete(AIDependencies.DEFENSE_REGEN_1_TECH)) and (cur_troops * AIDependencies.PROT_FOCUS_MULTIPLIER < max_troops - 2)): use_protection = False reason = "B2" elif max(max_shield, max_troops, max_defense) < 3: # joke defenses, don't bother with protection focus use_protection = False reason = "C" elif regional_threat and local_production_diff <= 2.0: use_protection = True reason = "D" elif safety_factor * regional_threat <= local_fleet_rating: use_protection = False reason = "E" elif safety_factor * regional_threat <= combined_local_defenses and ( pinfo.current_focus != PROTECTION or (0.5 * safety_factor * regional_threat <= local_fleet_rating and fleet_threat == 0 and neighbor_threat < combined_local_defenses and local_production_diff > 5)): use_protection = False reason = "F" elif (regional_threat <= combine_ratings(local_fleet_rating, adjusted_p_defenses) and safety_factor * regional_threat <= combine_ratings( my_neighbor_rating, local_fleet_rating, adjusted_p_defenses) and local_production_diff > 5): use_protection = False reason = "G" if use_protection or pinfo.current_focus == PROTECTION: debug( "Advising %sProtection Focus (reason %s) for planet %s, with local_prod_diff of %.1f, comb. local" " defenses %.1f, local fleet rating %.1f and regional threat %.1f, threat sources: %s", ["dropping ", ""][use_protection], reason, this_planet, local_production_diff, combined_local_defenses, local_fleet_rating, regional_threat, sys_status["regional_fleet_threats"], ) return use_protection