diff --git a/changelog.md b/changelog.md index a8350c4c..ffd51f2b 100644 --- a/changelog.md +++ b/changelog.md @@ -28,6 +28,7 @@ Saves from 4.0.0 are compatible with 4.1.0. * **[Economy]** EWRs can now be bought and sold for the correct price and can no longer be used to generate money * **[Flight Planning]** Fixed potential issue with angles > 360° or < 0° being generated when summing two angles. * **[Mission Generation]** The lua data for other plugins is now generated correctly +* **[Mission Generation]** Fixed problem with opfor planning missions against sold ground objects like SAMs * **[Mission Generation]** The legacy always-available tanker option no longer prevents mission creation. * **[Mission Generation]** Fix occasional KeyError preventing mission generation when all units of the same type in a convoy were killed. * **[UI]** Statistics window tick marks are now always integers. diff --git a/game/game.py b/game/game.py index 6d2aa329..11cc45ba 100644 --- a/game/game.py +++ b/game/game.py @@ -386,26 +386,49 @@ class Game: self.blue_bullseye = Bullseye(enemy_cp.position) self.red_bullseye = Bullseye(player_cp.position) - def initialize_turn(self) -> None: + def initialize_turn(self, for_red: bool = True, for_blue: bool = True): self.events = [] self._generate_events() - self.set_bullseye() # Update statistics self.game_stats.update(self) - self.blue_air_wing.reset() - self.red_air_wing.reset() - self.aircraft_inventory.reset() - for cp in self.theater.controlpoints: - self.aircraft_inventory.set_from_control_point(cp) - # Check for win or loss condition turn_state = self.check_win_loss() if turn_state in (TurnState.LOSS, TurnState.WIN): return self.process_win_loss(turn_state) + # Plan Coalition specific turn + if for_red: + self.initialize_turn_for(player=False) + if for_blue: + self.initialize_turn_for(player=True) + + # Plan GroundWar + for cp in self.theater.controlpoints: + if cp.has_frontline: + gplanner = GroundPlanner(cp, self) + gplanner.plan_groundwar() + self.ground_planners[cp.id] = gplanner + + def initialize_turn_for(self, player: bool) -> None: + + self.ato_for(player).clear() + self.air_wing_for(player).reset() + + self.aircraft_inventory.reset() + for cp in self.theater.controlpoints: + self.aircraft_inventory.set_from_control_point(cp) + # Refund all pending deliveries for opfor and if player + # has automate_aircraft_reinforcements + if (not player and not cp.captured) or ( + player + and cp.captured + and self.settings.automate_aircraft_reinforcements + ): + cp.pending_unit_deliveries.refund_all(self) + # Plan flights & combat for next turn with logged_duration("Computing conflict positions"): self.compute_conflicts_position() @@ -415,55 +438,48 @@ class Game: self.compute_transit_networks() self.ground_planners = {} - self.blue_procurement_requests.clear() - self.red_procurement_requests.clear() + self.procurement_requests_for(player).clear() with logged_duration("Procurement of airlift assets"): self.transfers.order_airlift_assets() with logged_duration("Transport planning"): self.transfers.plan_transports() - with logged_duration("Blue mission planning"): - if self.settings.auto_ato_behavior is not AutoAtoBehavior.Disabled: - blue_planner = CoalitionMissionPlanner(self, is_player=True) - blue_planner.plan_missions() + if not player or ( + player and self.settings.auto_ato_behavior is not AutoAtoBehavior.Disabled + ): + color = "Blue" if player else "Red" + with logged_duration(f"{color} mission planning"): + mission_planner = CoalitionMissionPlanner(self, player) + mission_planner.plan_missions() - with logged_duration("Red mission planning"): - red_planner = CoalitionMissionPlanner(self, is_player=False) - red_planner.plan_missions() + self.plan_procurement_for(player) - for cp in self.theater.controlpoints: - if cp.has_frontline: - gplanner = GroundPlanner(cp, self) - gplanner.plan_groundwar() - self.ground_planners[cp.id] = gplanner - - self.plan_procurement() - - def plan_procurement(self) -> None: + def plan_procurement_for(self, for_player: bool) -> None: # The first turn needs to buy a *lot* of aircraft to fill CAPs, so it # 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. After that the budget will be spend proportionally based on how much is already invested - self.budget = ProcurementAi( - self, - for_player=True, - faction=self.player_faction, - manage_runways=self.settings.automate_runway_repair, - manage_front_line=self.settings.automate_front_line_reinforcements, - manage_aircraft=self.settings.automate_aircraft_reinforcements, - ).spend_budget(self.budget) - - self.enemy_budget = ProcurementAi( - self, - for_player=False, - faction=self.enemy_faction, - manage_runways=True, - manage_front_line=True, - manage_aircraft=True, - ).spend_budget(self.enemy_budget) + if for_player: + self.budget = ProcurementAi( + self, + for_player=True, + faction=self.player_faction, + manage_runways=self.settings.automate_runway_repair, + manage_front_line=self.settings.automate_front_line_reinforcements, + manage_aircraft=self.settings.automate_aircraft_reinforcements, + ).spend_budget(self.budget) + else: + self.enemy_budget = ProcurementAi( + self, + for_player=False, + faction=self.enemy_faction, + manage_runways=True, + manage_front_line=True, + manage_aircraft=True, + ).spend_budget(self.enemy_budget) def message(self, text: str) -> None: self.informations.append(Information(text, turn=self.turn)) diff --git a/qt_ui/windows/basemenu/QBaseMenu2.py b/qt_ui/windows/basemenu/QBaseMenu2.py index 8a913280..5361350e 100644 --- a/qt_ui/windows/basemenu/QBaseMenu2.py +++ b/qt_ui/windows/basemenu/QBaseMenu2.py @@ -124,7 +124,6 @@ class QBaseMenu2(QDialog): self.cp.capture(self.game_model.game, for_player=not self.cp.captured) # Reinitialized ground planners and the like. The ATO needs to be reset because # missions planned against the flipped base are no longer valid. - self.game_model.game.reset_ato() self.game_model.game.initialize_turn() GameUpdateSignal.get_instance().updateGame(self.game_model.game) diff --git a/qt_ui/windows/basemenu/ground_forces/QGroundForcesStrategy.py b/qt_ui/windows/basemenu/ground_forces/QGroundForcesStrategy.py index ec467b92..166f7b4b 100644 --- a/qt_ui/windows/basemenu/ground_forces/QGroundForcesStrategy.py +++ b/qt_ui/windows/basemenu/ground_forces/QGroundForcesStrategy.py @@ -56,6 +56,5 @@ class QGroundForcesStrategy(QGroupBox): self.cp.base.affect_strength(amount) enemy_point.base.affect_strength(-amount) # Clear the ATO to replan missions affected by the front line. - self.game.reset_ato() self.game.initialize_turn() GameUpdateSignal.get_instance().updateGame(self.game) diff --git a/qt_ui/windows/groundobject/QGroundObjectMenu.py b/qt_ui/windows/groundobject/QGroundObjectMenu.py index 7f955f3d..46b2cb53 100644 --- a/qt_ui/windows/groundobject/QGroundObjectMenu.py +++ b/qt_ui/windows/groundobject/QGroundObjectMenu.py @@ -259,6 +259,14 @@ class QGroundObjectMenu(QDialog): self.update_total_value() self.game.budget = self.game.budget + self.total_value self.ground_object.groups = [] + + # Replan if the tgo was a target of the redfor + if any( + package.target == self.ground_object + for package in self.game.ato_for(player=False).packages + ): + self.game.initialize_turn(for_red=True, for_blue=False) + self.do_refresh_layout() GameUpdateSignal.get_instance().updateGame(self.game) @@ -439,6 +447,9 @@ class QBuyGroupForGroundObjectDialog(QDialog): ) self.ground_object.groups = [group] + # Replan redfor missions + self.game.initialize_turn(for_red=True, for_blue=False) + GameUpdateSignal.get_instance().updateGame(self.game) def buySam(self): @@ -452,6 +463,9 @@ class QBuyGroupForGroundObjectDialog(QDialog): self.ground_object.groups = list(sam_generator.groups) + # Replan redfor missions + self.game.initialize_turn(for_red=True, for_blue=False) + GameUpdateSignal.get_instance().updateGame(self.game) def buy_ewr(self): @@ -465,6 +479,9 @@ class QBuyGroupForGroundObjectDialog(QDialog): self.ground_object.groups = [ewr_generator.vg] + # Replan redfor missions + self.game.initialize_turn(for_red=True, for_blue=False) + GameUpdateSignal.get_instance().updateGame(self.game) def error_money(self):