File: calculate_production.py

package info (click to toggle)
freeorion 0.5.1-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 194,940 kB
  • sloc: cpp: 186,508; python: 40,969; ansic: 1,164; xml: 719; makefile: 32; sh: 7
file content (206 lines) | stat: -rw-r--r-- 9,492 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
import freeOrionAIInterface as fo

import AIDependencies
from buildings import BuildingType
from colonization.colony_score import debug_rating
from freeorion_tools import get_named_real, get_species_industry, tech_soon_available
from freeorion_tools.bonus_calculation import Bonus
from freeorion_tools.caching import cache_for_current_turn
from turn_state import have_honeycomb


def calculate_production(planet: fo.planet, species: fo.species, max_population: float, stability: float) -> float:
    """
    Calculate how much PP the planet's population could generate with industry focus.
    This only considers values that actually rely on industry focus, those that do not are handled by
    calculate_planet_colonization_rating._rate_focus_independent.
    """
    if stability <= 0.0:
        return 0.0

    bonus_modified = _get_production_bonus_modified(planet, stability)
    skill_multiplier = get_species_industry(species.name)
    bonus_by_policy = _get_production_bonus_mod_by_policy(stability)
    policy_multiplier = _get_policy_multiplier(stability)
    bonus_unmodified = _get_production_bonus_unmodified(planet, stability)
    bonus_flat = _get_production_flat(planet, stability)
    per_population = (
        (AIDependencies.INDUSTRY_PER_POP + bonus_modified) * skill_multiplier + bonus_by_policy
    ) * policy_multiplier + bonus_unmodified
    result = max_population * per_population + bonus_flat
    debug_rating(
        f"calculate_production pop={max_population:.2f}, st={stability:.2f}, b1={bonus_modified:.2f}, "
        f"m1={skill_multiplier:.2f}, b2={bonus_by_policy:.2f}, m2={policy_multiplier:.2f}, "
        f"b3={bonus_unmodified:.2f}, flat={bonus_flat:.2f} -> {result:.2f}"
    )
    return result


@cache_for_current_turn
def _get_modified_industry_bonuses() -> list[Bonus]:
    """
    Get list of per-population bonuses which are added before multiplication with the species skill value.
    """
    # TBD check connection!?
    have_center = bool(BuildingType.INDUSTRY_CENTER.built_or_queued_at())
    centre_bonus1 = get_named_real("BLD_INDUSTRY_CENTER_1_TARGET_INDUSTRY_PERPOP")
    centre_bonus2 = get_named_real("BLD_INDUSTRY_CENTER_2_TARGET_INDUSTRY_PERPOP")
    centre_bonus3 = get_named_real("BLD_INDUSTRY_CENTER_3_TARGET_INDUSTRY_PERPOP")
    return [
        Bonus(
            tech_soon_available("PRO_FUSION_GEN", 3),
            get_named_real("PRO_FUSION_GEN_MIN_STABILITY"),
            get_named_real("PRO_FUSION_GEN_TARGET_INDUSTRY_PERPOP"),
        ),
        Bonus(
            tech_soon_available("GRO_ENERGY_META", 3),
            get_named_real("GRO_ENERGY_META_MIN_STABILITY"),
            get_named_real("GRO_ENERGY_META_TARGET_INDUSTRY_PERPOP"),
        ),
        # special case: these are not cumulative
        Bonus(
            have_center and tech_soon_available("PRO_INDUSTRY_CENTER_III", 1),  # expensive research
            get_named_real("BLD_INDUSTRY_CENTER_3_MIN_STABILITY"),
            centre_bonus3 - centre_bonus2,
        ),
        Bonus(
            have_center and tech_soon_available("PRO_INDUSTRY_CENTER_II", 2),
            get_named_real("BLD_INDUSTRY_CENTER_2_MIN_STABILITY"),
            centre_bonus2 - centre_bonus1,
        ),
        Bonus(
            # normally we have the tech when we have the building, but we could have conquered one
            have_center and tech_soon_available("PRO_INDUSTRY_CENTER_II", 3),
            get_named_real("BLD_INDUSTRY_CENTER_1_MIN_STABILITY"),
            centre_bonus1,
        ),
    ]


def _get_production_bonus_modified(planet: fo.planet, stability: float) -> float:
    """
    Calculate bonus production per population which would be added before multiplication with the species skill value.
    """
    specials_bonus = sum(
        AIDependencies.INDUSTRY_PER_POP for s in planet.specials if s in AIDependencies.industry_boost_specials_modified
    )
    return specials_bonus + sum(bonus.get_bonus(stability) for bonus in _get_modified_industry_bonuses())


def _get_policy_multiplier(stability) -> float:
    if fo.getEmpire().policyAdopted("INDUSTRIALISM") and stability >= get_named_real("PLC_INDUSTRIALISM_MIN_STABILITY"):
        return 1.0 + get_named_real("PLC_INDUSTRIALISM_TARGET_INDUSTRY_PERCENT")
    return 1.0


def _get_production_bonus_mod_by_policy(stability: float) -> float:
    """
    Calculate bonus production per population which we would get independent of the species production skill,
    but still affected by industrialism.
    """
    # TBD: check connections?
    bonuses = [
        Bonus(
            bool(BuildingType.BLACK_HOLE_POW_GEN.built_or_queued_at()),
            get_named_real("BLD_BLACK_HOLE_POW_GEN_MIN_STABILITY"),
            get_named_real("BLD_BLACK_HOLE_POW_GEN_TARGET_INDUSTRY_PERPOP"),
        ),
    ]
    return sum(bonus.get_bonus(stability) for bonus in bonuses)


def _get_production_bonus_unmodified(planet: fo.planet, stability: float) -> float:
    """
    Calculate bonus production per population which we would get independent of the species production skill.
    """
    specials_bonus = sum(
        # growth.macros: STANDARD_INDUSTRY_BOOST
        AIDependencies.INDUSTRY_PER_POP
        for s in planet.specials
        if s in AIDependencies.industry_boost_specials_unmodified
    )

    # TBD: check connections?
    bonuses = [
        Bonus(
            bool(BuildingType.SOL_ORB_GEN.built_or_queued_at()),  # TBD: check star type
            get_named_real("BLD_SOL_ORB_GEN_MIN_STABILITY"),
            get_named_real("BLD_SOL_ORB_GEN_BRIGHT_TARGET_INDUSTRY_PERPOP"),
        ),
        Bonus(
            have_honeycomb(),
            0.0,
            get_named_real("HONEYCOMB_TARGET_INDUSTRY_PERPOP"),
        ),
        # TBD: Collective Thought Network? Ignored for now, AI doesn't know how to handle it.
        # It shouldn't make a big difference for selecting which planets to colonise anyway.
    ]
    return specials_bonus + sum(bonus.get_bonus(stability) for bonus in bonuses)


def _get_production_flat(planet: fo.planet, stability: float) -> float:
    """
    Calculate population independent production bonus.
    """
    value = _get_asteroid_and_ggg_value(planet, stability)
    # this is a rather expensive one, so consider it only if it is on top of the list
    if tech_soon_available(AIDependencies.PRO_AUTO_2, 1):
        value += get_named_real("PRO_SENTIENT_AUTO_TARGET_INDUSTRY_FLAT")
    return value


def _get_asteroid_and_ggg_value(planet: fo.planet, stability: float) -> float:
    """
    Calculate an estimate of the bonus we may get from asteroids (microgravity) and gas giant generators.
    """
    universe = fo.getUniverse()
    system = universe.getSystem(planet.systemID)
    ast_min_stability = get_named_real("PRO_MICROGRAV_MAN_MIN_STABILITY")
    count_asteroids = ast_min_stability <= stability and tech_soon_available(AIDependencies.PRO_MICROGRAV_MAN, 5)
    ggg_min_stability = get_named_real("BLD_GAS_GIANT_GEN_MIN_STABILITY")
    count_ggg = ggg_min_stability <= stability and tech_soon_available(AIDependencies.PRO_ORBITAL_GEN, 5)
    asteroid_value = 0.0
    ggg_value = 0.0
    for pid in system.planetIDs:
        p2 = universe.getPlanet(pid)
        if p2:
            if count_asteroids and p2.size == fo.planetSize.asteroids:
                full_bonus = get_named_real("PRO_MICROGRAV_MAN_TARGET_INDUSTRY_FLAT")
                if p2.owner == fo.empireID or pid == planet.id:
                    asteroid_value = full_bonus
                # If we cannot see it, we cannot put an outpost on it, although in case of asteroids it likely
                # only hidden temporarily.
                elif universe.getVisibility(pid, fo.empireID()) and p2.unowned:
                    asteroid_value = max(asteroid_value, 0.5 * full_bonus)
                # TODO prospective_invasion_targets?
            if count_ggg:
                ggg_value = max(ggg_value, _ggg_value(planet, p2))
    debug_rating(
        f"_get_asteroid_and_ggg_value amin={ast_min_stability:.1f}->{count_asteroids:.1f}, "
        f"gmin={ggg_min_stability:.1f}->{count_ggg:.1f}, aval={asteroid_value:.1f}, gval={ggg_value:.1f}"
    )
    return asteroid_value + ggg_value


def _ggg_value(planet: fo.planet, p2: fo.planet) -> float:
    """
    Check if p2 not planet and is a gas giant. If determines a value for the potential GGG on p2.
    """
    universe = fo.getUniverse()
    if p2.id != planet.id and p2.size == fo.planetSize.gasGiant:
        ggg_colony_flat = get_named_real("BLD_GAS_GIANT_GEN_COLONY_TARGET_INDUSTRY_FLAT")
        ggg_outpost_flat = get_named_real("BLD_GAS_GIANT_GEN_OUTPOST_TARGET_INDUSTRY_FLAT")
        # species living on a GG do not like a GGG, so we ignore the planet itself here
        if p2.owner == fo.empireID():
            if not p2.speciesName:
                return ggg_outpost_flat
            if p2.id in BuildingType.GAS_GIANT_GEN.built_or_queued_at():
                return ggg_colony_flat
            else:
                return 0.4 * ggg_colony_flat  # we could build one, but inhabitants won't like it
        # If we cannot see it, we cannot put an outpost on it, most likely it is not really unowned
        if universe.getVisibility(p2.id, fo.empireID()) and p2.unowned:
            return 0.5 * ggg_colony_flat  # unowned means we could probably get it easily
        # TODO prospective_invasion_targets?
        #  So far we do not consider owned gas giant for planets here
    return 0.0