mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Retreat ground forces from captured bases.
If the captured base has no connection to other friendly objectives (the base was encircled) then the enemy equipment will be captured and sold. Otherwise the units will retreat toward connected bases. Partial fix for https://github.com/Khopa/dcs_liberation/issues/693. Still need to fix this for air units.
This commit is contained in:
parent
6cbc2b707a
commit
fcb1d8e104
@ -14,6 +14,7 @@ Saves from 2.3 are not compatible with 2.4.
|
||||
* **[Mission Generator]** Multiple groups are created for complex SAM sites (SAMs with additional point defense or SHORADS), improving Skynet behavior.
|
||||
* **[Skynet]** Point defenses are now configured to remain on to protect the site they accompany.
|
||||
* **[Balance]** Opfor now gains income using the same rules as the player, significantly increasing their income relative to the player for most campaigns.
|
||||
* **[Balance]** Ground units now retreat from captured bases when they are connected to a friendly base. Units with no retreat path will be captured and sold.
|
||||
* **[Economy]** FOBs generate only $10M per turn (previously $20M like airbases).
|
||||
* **[Economy]** Carriers and off-map spawns generate no income (previously $20M like airbases).
|
||||
* **[UI]** Multi-SAM objectives now show threat and detection rings per group.
|
||||
|
||||
@ -9,6 +9,7 @@ from dcs.unittype import FlyingType, UnitType, VehicleType
|
||||
from dcs.vehicles import AirDefence, Armor
|
||||
|
||||
from game import db
|
||||
from game.db import PRICES
|
||||
from gen.ground_forces.ai_ground_planner_db import TYPE_SHORAD
|
||||
|
||||
STRENGTH_AA_ASSEMBLE_MIN = 0.2
|
||||
@ -37,6 +38,16 @@ class Base:
|
||||
def total_armor(self) -> int:
|
||||
return sum(self.armor.values())
|
||||
|
||||
@property
|
||||
def total_armor_value(self) -> int:
|
||||
total = 0
|
||||
for unit_type, count in self.armor.items():
|
||||
try:
|
||||
total += PRICES[unit_type] * count
|
||||
except KeyError:
|
||||
logging.exception(f"No price found for {unit_type.id}")
|
||||
return total
|
||||
|
||||
@property
|
||||
def total_frontline_aa(self) -> int:
|
||||
return sum([v for k, v in self.armor.items() if k in TYPE_SHORAD])
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import heapq
|
||||
import itertools
|
||||
import logging
|
||||
import random
|
||||
@ -7,7 +8,8 @@ import re
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from typing import Dict, Iterator, List, Optional, TYPE_CHECKING, Type
|
||||
from functools import total_ordering
|
||||
from typing import Any, Dict, Iterator, List, Optional, TYPE_CHECKING, Type
|
||||
|
||||
from dcs.mapping import Point
|
||||
from dcs.ships import (
|
||||
@ -192,6 +194,28 @@ class RunwayStatus:
|
||||
return f"Runway repairing, {turns_remaining} turns remaining"
|
||||
|
||||
|
||||
@total_ordering
|
||||
class GroundUnitDestination:
|
||||
def __init__(self, control_point: ControlPoint) -> None:
|
||||
self.control_point = control_point
|
||||
|
||||
@property
|
||||
def total_value(self) -> float:
|
||||
return self.control_point.base.total_armor_value
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
if not isinstance(other, GroundUnitDestination):
|
||||
raise TypeError
|
||||
|
||||
return self.total_value == other.total_value
|
||||
|
||||
def __lt__(self, other: Any) -> bool:
|
||||
if not isinstance(other, GroundUnitDestination):
|
||||
raise TypeError
|
||||
|
||||
return self.total_value < other.total_value
|
||||
|
||||
|
||||
class ControlPoint(MissionTarget, ABC):
|
||||
|
||||
position = None # type: Point
|
||||
@ -365,9 +389,37 @@ class ControlPoint(MissionTarget, ABC):
|
||||
base_defense.position)
|
||||
self.base_defenses = []
|
||||
|
||||
def capture_equipment(self, game: Game) -> None:
|
||||
total = self.base.total_armor_value
|
||||
self.base.armor.clear()
|
||||
game.adjust_budget(total, player=not self.captured)
|
||||
game.message(
|
||||
f"{self.name} is not connected to any friendly points. Ground "
|
||||
f"vehicles have been captured and sold for ${total}M.")
|
||||
|
||||
def retreat_ground_units(self, game: Game):
|
||||
# When there are multiple valid destinations, deliver units to whichever
|
||||
# base is least defended first. The closest approximation of unit
|
||||
# strength we have is price
|
||||
destinations = [GroundUnitDestination(cp)
|
||||
for cp in self.connected_points
|
||||
if cp.captured == self.captured]
|
||||
if not destinations:
|
||||
self.capture_equipment(game)
|
||||
return
|
||||
|
||||
heapq.heapify(destinations)
|
||||
destination = heapq.heappop(destinations)
|
||||
while self.base.armor:
|
||||
unit_type, count = self.base.armor.popitem()
|
||||
for _ in range(count):
|
||||
destination.control_point.base.commision_units({unit_type: 1})
|
||||
destination = heapq.heappushpop(destinations, destination)
|
||||
|
||||
# TODO: Should be Airbase specific.
|
||||
def capture(self, game: Game, for_player: bool) -> None:
|
||||
self.pending_unit_deliveries.refund_all(game)
|
||||
self.retreat_ground_units(game)
|
||||
|
||||
if for_player:
|
||||
self.captured = True
|
||||
@ -377,7 +429,6 @@ class ControlPoint(MissionTarget, ABC):
|
||||
self.base.set_strength_to_minimum()
|
||||
|
||||
self.base.aircraft = {}
|
||||
self.base.armor = {}
|
||||
|
||||
self.clear_base_defenses()
|
||||
from .start_generator import BaseDefenseGenerator
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user