mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Split purchase budget based on investment ratio.
The AI purchaser will aim to have a 50/50 ground/air investment mix. This allows it to overspend on one category if significant losses were taken the previous turn. The total purchase amount is still limited, so if the bases are full when only 10% of the investment is in ground units, the full budget for the turn will still go to air.
This commit is contained in:
@@ -438,8 +438,8 @@ class Game:
|
||||
# gets much more of the budget that turn. Otherwise budget (after
|
||||
# repairs) is split evenly between air and ground. For the default
|
||||
# starting budget of 2000 this gives 600 to ground forces and 1400 to
|
||||
# aircraft.
|
||||
ground_portion = 0.3 if self.turn == 0 else 0.5
|
||||
# aircraft. After that the budget will be spend proportionally based on how much is already invested
|
||||
|
||||
self.budget = ProcurementAi(
|
||||
self,
|
||||
for_player=True,
|
||||
@@ -447,7 +447,6 @@ class Game:
|
||||
manage_runways=self.settings.automate_runway_repair,
|
||||
manage_front_line=self.settings.automate_front_line_reinforcements,
|
||||
manage_aircraft=self.settings.automate_aircraft_reinforcements,
|
||||
front_line_budget_share=ground_portion,
|
||||
).spend_budget(self.budget)
|
||||
|
||||
self.enemy_budget = ProcurementAi(
|
||||
@@ -457,7 +456,6 @@ class Game:
|
||||
manage_runways=True,
|
||||
manage_front_line=True,
|
||||
manage_aircraft=True,
|
||||
front_line_budget_share=ground_portion,
|
||||
).spend_budget(self.enemy_budget)
|
||||
|
||||
def message(self, text: str) -> None:
|
||||
|
||||
@@ -8,14 +8,13 @@ from typing import Iterator, List, Optional, TYPE_CHECKING, Tuple, Type
|
||||
from dcs.unittype import FlyingType, VehicleType
|
||||
|
||||
from game import db
|
||||
from game.data.groundunitclass import GroundUnitClass
|
||||
from game.factions.faction import Faction
|
||||
from game.theater import ControlPoint, MissionTarget
|
||||
from game.utils import Distance
|
||||
from gen.flights.ai_flight_planner_db import aircraft_for_task
|
||||
from gen.flights.closestairfields import ObjectiveDistanceCache
|
||||
from gen.flights.flight import FlightType
|
||||
from game.data.groundunitclass import GroundUnitClass
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game import Game
|
||||
@@ -46,10 +45,7 @@ class ProcurementAi:
|
||||
manage_runways: bool,
|
||||
manage_front_line: bool,
|
||||
manage_aircraft: bool,
|
||||
front_line_budget_share: float,
|
||||
) -> None:
|
||||
if front_line_budget_share > 1.0:
|
||||
raise ValueError
|
||||
|
||||
self.game = game
|
||||
self.is_player = for_player
|
||||
@@ -58,14 +54,34 @@ class ProcurementAi:
|
||||
self.manage_runways = manage_runways
|
||||
self.manage_front_line = manage_front_line
|
||||
self.manage_aircraft = manage_aircraft
|
||||
self.front_line_budget_share = front_line_budget_share
|
||||
self.threat_zones = self.game.threat_zone_for(not self.is_player)
|
||||
|
||||
def calculate_ground_unit_budget_share(self) -> float:
|
||||
armor_investment = 0
|
||||
aircraft_investment = 0
|
||||
for cp in self.owned_points:
|
||||
cp_ground_units = cp.allocated_ground_units(self.game.transfers)
|
||||
armor_investment += cp_ground_units.total_value
|
||||
cp_aircraft = cp.allocated_aircraft(self.game)
|
||||
aircraft_investment += cp_aircraft.total_value
|
||||
|
||||
total_investment = aircraft_investment + armor_investment
|
||||
if total_investment == 0:
|
||||
# Turn 0 or all units were destroyed. Either way, split 50/50.
|
||||
return 0.5
|
||||
|
||||
# the more planes we have, the more ground units we want and vice versa
|
||||
ground_unit_share = aircraft_investment / total_investment
|
||||
if ground_unit_share > 1.0:
|
||||
raise ValueError
|
||||
|
||||
return ground_unit_share
|
||||
|
||||
def spend_budget(self, budget: float) -> float:
|
||||
if self.manage_runways:
|
||||
budget = self.repair_runways(budget)
|
||||
if self.manage_front_line:
|
||||
armor_budget = math.ceil(budget * self.front_line_budget_share)
|
||||
armor_budget = budget * self.calculate_ground_unit_budget_share()
|
||||
budget -= armor_budget
|
||||
budget += self.reinforce_front_line(armor_budget)
|
||||
|
||||
|
||||
@@ -124,14 +124,38 @@ class PresetLocations:
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PendingOccupancy:
|
||||
present: int
|
||||
ordered: int
|
||||
transferring: int
|
||||
class AircraftAllocations:
|
||||
present: dict[Type[FlyingType], int]
|
||||
ordered: dict[Type[FlyingType], int]
|
||||
transferring: dict[Type[FlyingType], int]
|
||||
|
||||
@property
|
||||
def total_value(self) -> int:
|
||||
total: int = 0
|
||||
for unit_type, count in self.present.items():
|
||||
total += PRICES[unit_type] * count
|
||||
for unit_type, count in self.ordered.items():
|
||||
total += PRICES[unit_type] * count
|
||||
for unit_type, count in self.transferring.items():
|
||||
total += PRICES[unit_type] * count
|
||||
|
||||
return total
|
||||
|
||||
@property
|
||||
def total(self) -> int:
|
||||
return self.present + self.ordered + self.transferring
|
||||
return self.total_present + self.total_ordered + self.total_transferring
|
||||
|
||||
@property
|
||||
def total_present(self) -> int:
|
||||
return sum(self.present.values())
|
||||
|
||||
@property
|
||||
def total_ordered(self) -> int:
|
||||
return sum(self.ordered.values())
|
||||
|
||||
@property
|
||||
def total_transferring(self) -> int:
|
||||
return sum(self.transferring.values())
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -149,6 +173,18 @@ class GroundUnitAllocations:
|
||||
combined[unit_type] += count
|
||||
return dict(combined)
|
||||
|
||||
@property
|
||||
def total_value(self) -> int:
|
||||
total: int = 0
|
||||
for unit_type, count in self.present.items():
|
||||
total += PRICES[unit_type] * count
|
||||
for unit_type, count in self.ordered.items():
|
||||
total += PRICES[unit_type] * count
|
||||
for unit_type, count in self.transferring.items():
|
||||
total += PRICES[unit_type] * count
|
||||
|
||||
return total
|
||||
|
||||
@cached_property
|
||||
def total(self) -> int:
|
||||
return self.total_present + self.total_ordered + self.total_transferring
|
||||
@@ -585,37 +621,25 @@ class ControlPoint(MissionTarget, ABC):
|
||||
def can_operate(self, aircraft: Type[FlyingType]) -> bool:
|
||||
...
|
||||
|
||||
def aircraft_transferring(self, game: Game) -> int:
|
||||
def aircraft_transferring(self, game: Game) -> dict[Type[FlyingType], int]:
|
||||
if self.captured:
|
||||
ato = game.blue_ato
|
||||
else:
|
||||
ato = game.red_ato
|
||||
|
||||
total = 0
|
||||
transferring: defaultdict[Type[FlyingType], int] = defaultdict(int)
|
||||
for package in ato.packages:
|
||||
for flight in package.flights:
|
||||
if flight.departure == flight.arrival:
|
||||
continue
|
||||
if flight.departure == self:
|
||||
total -= flight.count
|
||||
transferring[flight.unit_type] -= flight.count
|
||||
elif flight.arrival == self:
|
||||
total += flight.count
|
||||
return total
|
||||
|
||||
def expected_aircraft_next_turn(self, game: Game) -> PendingOccupancy:
|
||||
on_order = 0
|
||||
for unit_bought in self.pending_unit_deliveries.units:
|
||||
if issubclass(unit_bought, FlyingType):
|
||||
on_order += self.pending_unit_deliveries.units[unit_bought]
|
||||
|
||||
return PendingOccupancy(
|
||||
self.base.total_aircraft, on_order, self.aircraft_transferring(game)
|
||||
)
|
||||
transferring[flight.unit_type] += flight.count
|
||||
return transferring
|
||||
|
||||
def unclaimed_parking(self, game: Game) -> int:
|
||||
return (
|
||||
self.total_aircraft_parking - self.expected_aircraft_next_turn(game).total
|
||||
)
|
||||
return self.total_aircraft_parking - self.allocated_aircraft(game).total
|
||||
|
||||
@abstractmethod
|
||||
def active_runway(
|
||||
@@ -665,6 +689,16 @@ class ControlPoint(MissionTarget, ABC):
|
||||
u.position.x = u.position.x + delta.x
|
||||
u.position.y = u.position.y + delta.y
|
||||
|
||||
def allocated_aircraft(self, game: Game) -> AircraftAllocations:
|
||||
on_order = {}
|
||||
for unit_bought, count in self.pending_unit_deliveries.units.items():
|
||||
if issubclass(unit_bought, FlyingType):
|
||||
on_order[unit_bought] = count
|
||||
|
||||
return AircraftAllocations(
|
||||
self.base.aircraft, on_order, self.aircraft_transferring(game)
|
||||
)
|
||||
|
||||
def allocated_ground_units(
|
||||
self, transfers: PendingTransfers
|
||||
) -> GroundUnitAllocations:
|
||||
|
||||
Reference in New Issue
Block a user