diff --git a/game/db.py b/game/db.py index f8318923..d175d5c3 100644 --- a/game/db.py +++ b/game/db.py @@ -168,6 +168,7 @@ PRICES = { Armor.IFV_BMP_2: 16, Armor.IFV_BMP_3: 18, Armor.ZBD_04A: 12, + Armor.ZTZ_96B: 35, Armor.APC_Cobra: 4, Armor.APC_M113: 6, @@ -187,6 +188,17 @@ PRICES = { Armor.IFV_Marder: 10, Armor.IFV_MCV_80: 10, + Artillery.MLRS_M270: 55, + Artillery.SPH_M109_Paladin: 25, + + Artillery.SPH_2S9_Nona: 12, + Artillery.SPH_2S1_Gvozdika: 18, + Artillery.SPH_2S3_Akatsia: 24, + Artillery.SPH_2S19_Msta: 30, + Artillery.MLRS_BM_21_Grad: 15, + Artillery.MLRS_9K57_Uragan_BM_27: 40, + Artillery.MLRS_9A52_Smerch: 40, + Unarmed.Transport_UAZ_469: 3, Unarmed.Transport_Ural_375: 3, Infantry.Infantry_M4: 1, @@ -220,11 +232,14 @@ PRICES = { Armor.IFV_Sd_Kfz_234_2_Puma:4, Armor.MT_M4_Sherman:4, Armor.MT_M4A4_Sherman_Firefly:6, + Armor.CT_Cromwell_IV:8, Armor.M30_Cargo_Carrier:2, Armor.APC_M2A1:2, AirDefence.AAA_Bofors_40mm:4, AirDefence.AAA_Flak_36:6, AirDefence.AAA_Flak_18:4, + Artillery.M12_GMC:2, + Artillery.Sturmpanzer_IV_Brummbär:2, # ship CV_1143_5_Admiral_Kuznetsov: 100, @@ -341,8 +356,6 @@ UNIT_BY_TASK = { Armor.ARV_BRDM_2, Armor.ARV_BRDM_2, Armor.ARV_BRDM_2, - Armor.ARV_BRDM_2, - Armor.ARV_BRDM_2, Armor.ARV_BTR_RD, Armor.ARV_BTR_RD, Armor.ARV_BTR_RD, @@ -370,6 +383,7 @@ UNIT_BY_TASK = { Armor.MBT_T_80U, Armor.MBT_T_80U, Armor.MBT_T_90, + Armor.ZTZ_96B, Armor.APC_Cobra, Armor.APC_Cobra, @@ -424,6 +438,7 @@ UNIT_BY_TASK = { Armor.IFV_Sd_Kfz_234_2_Puma, Armor.MT_M4_Sherman, Armor.MT_M4A4_Sherman_Firefly, + Armor.CT_Cromwell_IV, Armor.M30_Cargo_Carrier, Armor.M30_Cargo_Carrier, Armor.APC_M2A1, @@ -431,6 +446,19 @@ UNIT_BY_TASK = { Armor.APC_M2A1, Armor.APC_M2A1, + Artillery.MLRS_M270, + Artillery.SPH_M109_Paladin, + Artillery.SPH_2S9_Nona, + Artillery.SPH_2S1_Gvozdika, + Artillery.SPH_2S3_Akatsia, + Artillery.SPH_2S19_Msta, + Artillery.MLRS_BM_21_Grad, + Artillery.MLRS_BM_21_Grad, + Artillery.MLRS_9K57_Uragan_BM_27, + Artillery.MLRS_9A52_Smerch, + Artillery.M12_GMC, + Artillery.Sturmpanzer_IV_Brummbär, + ], AirDefence: [ @@ -455,7 +483,7 @@ UNIT_BY_TASK = { Nothing: [Infantry.Infantry_M4, Infantry.Soldier_AK, ], Embarking: [], Carriage: [CVN_74_John_C__Stennis, LHA_1_Tarawa, CV_1143_5_Admiral_Kuznetsov, ], - CargoTransportation: [Dry_cargo_ship_Ivanov, Bulk_cargo_ship_Yakushev, Tanker_Elnya_160, Armed_speedboat, ], + CargoTransportation: [Dry_cargo_ship_Ivanov, Bulk_cargo_ship_Yakushev, Tanker_Elnya_160, Armed_speedboat, ] } """ @@ -801,6 +829,22 @@ def unit_task(unit: UnitType) -> Task: def find_unittype(for_task: Task, country_name: str) -> typing.List[UnitType]: return [x for x in UNIT_BY_TASK[for_task] if x in FACTIONS[country_name]["units"]] +def find_infantry(country_name: str) -> typing.List[UnitType]: + inf = [ + Infantry.Paratrooper_AKS, Infantry.Paratrooper_AKS, Infantry.Paratrooper_AKS, Infantry.Paratrooper_AKS, Infantry.Paratrooper_AKS, + Infantry.Soldier_RPG, + Infantry.Infantry_M4, Infantry.Infantry_M4, Infantry.Infantry_M4, Infantry.Infantry_M4, Infantry.Infantry_M4, + Infantry.Soldier_M249, + Infantry.Soldier_AK, Infantry.Soldier_AK, Infantry.Soldier_AK, Infantry.Soldier_AK, Infantry.Soldier_AK, + Infantry.Paratrooper_RPG_16, + Infantry.Georgian_soldier_with_M4, Infantry.Georgian_soldier_with_M4, Infantry.Georgian_soldier_with_M4, Infantry.Georgian_soldier_with_M4, + Infantry.Infantry_Soldier_Rus, Infantry.Infantry_Soldier_Rus, Infantry.Infantry_Soldier_Rus, Infantry.Infantry_Soldier_Rus, + Infantry.Infantry_SMLE_No_4_Mk_1, Infantry.Infantry_SMLE_No_4_Mk_1, Infantry.Infantry_SMLE_No_4_Mk_1, + Infantry.Infantry_Mauser_98, Infantry.Infantry_Mauser_98, Infantry.Infantry_Mauser_98, Infantry.Infantry_Mauser_98, + Infantry.Infantry_M1_Garand, Infantry.Infantry_M1_Garand, Infantry.Infantry_M1_Garand, + Infantry.Infantry_Soldier_Insurgents, Infantry.Infantry_Soldier_Insurgents, Infantry.Infantry_Soldier_Insurgents + ] + return [x for x in inf if x in FACTIONS[country_name]["units"]] def unit_type_name(unit_type) -> str: return unit_type.id and unit_type.id or unit_type.name @@ -825,7 +869,7 @@ def unit_type_of(unit: Unit) -> UnitType: elif isinstance(unit, Ship): return ship_map[unit.type] else: - return unit.unit_type + return unit.type def task_name(task) -> str: diff --git a/game/event/__init__.py b/game/event/__init__.py index 11025c6a..77ec75bb 100644 --- a/game/event/__init__.py +++ b/game/event/__init__.py @@ -1,10 +1,2 @@ from .event import * from .frontlineattack import * -from .frontlinepatrol import * -from .intercept import * -from .baseattack import * -from .navalintercept import * -from .insurgentattack import * -from .convoystrike import * -from .infantrytransport import * -from .strike import * diff --git a/game/event/baseattack.py b/game/event/baseattack.py deleted file mode 100644 index ef910c3c..00000000 --- a/game/event/baseattack.py +++ /dev/null @@ -1,106 +0,0 @@ -from game.operation.baseattack import BaseAttackOperation - -from .event import * -from game.db import assigned_units_from - - -class BaseAttackEvent(Event): - silent = True - BONUS_BASE = 15 - STRENGTH_RECOVERY = 0.55 - - def __str__(self): - return "Base attack" - - @property - def tasks(self): - return [CAP, CAS, PinpointStrike] - - def flight_name(self, for_task: typing.Type[Task]) -> str: - if for_task == CAP: - return "Escort flight" - elif for_task == CAS: - return "CAS flight" - elif for_task == PinpointStrike: - return "Ground attack" - - def is_successfull(self, debriefing: Debriefing): - - if self.game.player_name == self.attacker_name: - attacker_country = self.game.player_country - defender_country = self.game.enemy_country - else: - attacker_country = self.game.enemy_country - defender_country = self.game.player_country - - alive_attackers = sum([v for k, v in debriefing.alive_units.get(attacker_country, {}).items() if db.unit_task(k) == PinpointStrike]) - alive_defenders = sum([v for k, v in debriefing.alive_units.get(defender_country, {}).items() if db.unit_task(k) == PinpointStrike]) - attackers_success = alive_attackers >= alive_defenders - if self.departure_cp.captured: - return attackers_success - else: - return not attackers_success - - def commit(self, debriefing: Debriefing): - super(BaseAttackEvent, self).commit(debriefing) - if self.is_successfull(debriefing): - if self.departure_cp.captured: - self.to_cp.captured = True - self.to_cp.ground_objects = [] - self.to_cp.base.filter_units(db.FACTIONS[self.attacker_name]["units"]) - - self.to_cp.base.affect_strength(+self.STRENGTH_RECOVERY) - else: - if not self.departure_cp.captured: - self.to_cp.captured = False - self.to_cp.base.affect_strength(+self.STRENGTH_RECOVERY) - - def skip(self): - if not self.is_player_attacking and self.to_cp.captured: - self.to_cp.captured = False - - def player_defending(self, flights: db.TaskForceDict): - assert CAP in flights and len(flights) == 1, "Invalid scrambled flights" - - cas = self.departure_cp.base.scramble_cas(self.game.settings.multiplier) - escort = self.departure_cp.base.scramble_sweep(self.game.settings.multiplier) - attackers = self.departure_cp.base.armor - - op = BaseAttackOperation(game=self.game, - attacker_name=self.attacker_name, - defender_name=self.defender_name, - from_cp=self.from_cp, - departure_cp=self.departure_cp, - to_cp=self.to_cp) - - op.setup(cas=assigned_units_from(cas), - escort=assigned_units_from(escort), - intercept=flights[CAP], - attack=attackers, - defense=self.to_cp.base.armor, - aa=self.to_cp.base.aa) - - self.operation = op - - def player_attacking(self, flights: db.TaskForceDict): - assert CAP in flights and CAS in flights and PinpointStrike in flights and len(flights) == 3, "Invalid flights" - - op = BaseAttackOperation(game=self.game, - attacker_name=self.attacker_name, - defender_name=self.defender_name, - from_cp=self.from_cp, - departure_cp=self.departure_cp, - to_cp=self.to_cp) - - defenders = self.to_cp.base.scramble_last_defense() - #defenders.update(self.to_cp.base.scramble_cas(self.game.settings.multiplier)) - - op.setup(cas=flights[CAS], - escort=flights[CAP], - attack=unitdict_from(flights[PinpointStrike]), - intercept=assigned_units_from(defenders), - defense=self.to_cp.base.armor, - aa=self.to_cp.base.assemble_aa()) - - self.operation = op - diff --git a/game/event/convoystrike.py b/game/event/convoystrike.py deleted file mode 100644 index 5fb62d07..00000000 --- a/game/event/convoystrike.py +++ /dev/null @@ -1,89 +0,0 @@ -import math -import random - -from dcs.task import * - -from game import * -from game.event import * -from game.event.frontlineattack import FrontlineAttackEvent - -from .event import * -from game.operation.convoystrike import ConvoyStrikeOperation - -TRANSPORT_COUNT = 4, 6 -DEFENDERS_AMOUNT_FACTOR = 4 - - -class ConvoyStrikeEvent(Event): - SUCCESS_FACTOR = 0.6 - STRENGTH_INFLUENCE = 0.25 - - targets = None # type: db.ArmorDict - - @property - def threat_description(self): - return "" - - @property - def tasks(self): - return [CAS] - - @property - def global_cp_available(self) -> bool: - return True - - def flight_name(self, for_task: typing.Type[Task]) -> str: - if for_task == CAS: - return "Strike flight" - - def __str__(self): - return "Convoy Strike" - - def skip(self): - self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) - - def commit(self, debriefing: Debriefing): - super(ConvoyStrikeEvent, self).commit(debriefing) - - if self.from_cp.captured: - if self.is_successfull(debriefing): - self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) - else: - if self.is_successfull(debriefing): - self.from_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) - - def is_successfull(self, debriefing: Debriefing): - - if self.game.player_name == self.attacker_name: - defender_country = self.game.enemy_country - else: - defender_country = self.game.player_country - - killed_units = sum([v for k, v in debriefing.destroyed_units.get(defender_country, {}).items() if db.unit_task(k) in [PinpointStrike, Reconnaissance]]) - all_units = sum(self.targets.values()) - attackers_success = (float(killed_units) / (all_units + 0.01)) > self.SUCCESS_FACTOR - if self.from_cp.captured: - return attackers_success - else: - return not attackers_success - - def player_attacking(self, flights: db.TaskForceDict): - assert CAS in flights and len(flights) == 1, "Invalid flights" - - convoy_unittype = db.find_unittype(Reconnaissance, self.defender_name)[0] - defense_unittype = db.find_unittype(PinpointStrike, self.defender_name)[0] - - defenders_count = int(math.ceil(self.from_cp.base.strength * self.from_cp.importance * DEFENDERS_AMOUNT_FACTOR)) - self.targets = {convoy_unittype: random.randrange(*TRANSPORT_COUNT), - defense_unittype: defenders_count, } - - op = ConvoyStrikeOperation(game=self.game, - attacker_name=self.attacker_name, - defender_name=self.defender_name, - from_cp=self.from_cp, - departure_cp=self.departure_cp, - to_cp=self.to_cp) - op.setup(target=self.targets, - strikegroup=flights[CAS]) - - self.operation = op diff --git a/game/event/frontlinepatrol.py b/game/event/frontlinepatrol.py deleted file mode 100644 index 166d9a72..00000000 --- a/game/event/frontlinepatrol.py +++ /dev/null @@ -1,86 +0,0 @@ -from game.event import * -from game.operation.frontlinepatrol import FrontlinePatrolOperation -from userdata.debriefing import Debriefing - - -class FrontlinePatrolEvent(Event): - ESCORT_FACTOR = 0.5 - STRENGTH_INFLUENCE = 0.3 - SUCCESS_FACTOR = 0.8 - - cas = None # type: db.PlaneDict - escort = None # type: db.PlaneDict - - @property - def threat_description(self): - return "{} aircraft + ? CAS".format(self.to_cp.base.scramble_count(self.game.settings.multiplier * self.ESCORT_FACTOR, CAP)) - - @property - def tasks(self): - return [CAP] - - def flight_name(self, for_task: typing.Type[Task]) -> str: - if for_task == CAP: - return "CAP flight" - elif for_task == PinpointStrike: - return "Ground attack" - - def __str__(self): - return "Frontline CAP" - - def is_successfull(self, debriefing: Debriefing): - - if self.game.player_name == self.attacker_name: - attacker_country = self.game.player_country - defender_country = self.game.enemy_country - else: - attacker_country = self.game.enemy_country - defender_country = self.game.player_country - - alive_attackers = sum([v for k, v in debriefing.alive_units[attacker_country].items() if db.unit_task(k) == PinpointStrike]) - alive_defenders = sum([v for k, v in debriefing.alive_units[defender_country].items() if db.unit_task(k) == PinpointStrike]) - attackers_success = (float(alive_attackers) / (alive_defenders + 0.01)) >= self.SUCCESS_FACTOR - if self.from_cp.captured: - return attackers_success - else: - return not attackers_success - - def commit(self, debriefing: Debriefing): - super(FrontlinePatrolEvent, self).commit(debriefing) - - if self.from_cp.captured: - if self.is_successfull(debriefing): - self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) - else: - self.to_cp.base.affect_strength(+self.STRENGTH_INFLUENCE) - else: - if self.is_successfull(debriefing): - self.from_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) - else: - self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) - - def skip(self): - pass - - def player_attacking(self, flights: db.TaskForceDict): - assert CAP in flights and len(flights) == 1, "Invalid flights" - - self.cas = self.to_cp.base.scramble_cas(self.game.settings.multiplier) - self.escort = self.to_cp.base.scramble_sweep(self.game.settings.multiplier * self.ESCORT_FACTOR) - - op = FrontlinePatrolOperation(game=self.game, - attacker_name=self.attacker_name, - defender_name=self.defender_name, - from_cp=self.from_cp, - departure_cp=self.departure_cp, - to_cp=self.to_cp) - - defenders = self.to_cp.base.assemble_attack() - attackers = db.unitdict_restrict_count(self.from_cp.base.assemble_attack(), sum(defenders.values())) - op.setup(cas=assigned_units_from(self.cas), - escort=assigned_units_from(self.escort), - interceptors=flights[CAP], - armor_attackers=attackers, - armor_defenders=defenders) - - self.operation = op diff --git a/game/event/infantrytransport.py b/game/event/infantrytransport.py deleted file mode 100644 index 0ebebd06..00000000 --- a/game/event/infantrytransport.py +++ /dev/null @@ -1,56 +0,0 @@ -import math -import random - -from dcs.task import * -from dcs.vehicles import * - -from game import db -from game.operation.infantrytransport import InfantryTransportOperation -from theater.conflicttheater import * -from userdata.debriefing import Debriefing - -from .event import * - - -class InfantryTransportEvent(Event): - STRENGTH_INFLUENCE = 0.3 - - def __str__(self): - return "Frontline transport troops" - - @property - def tasks(self): - return [Embarking] - - def flight_name(self, for_task: typing.Type[Task]) -> str: - if for_task == Embarking: - return "Transport flight" - - def is_successfull(self, debriefing: Debriefing): - return True - - def commit(self, debriefing: Debriefing): - super(InfantryTransportEvent, self).commit(debriefing) - - if self.is_successfull(debriefing): - self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) - else: - self.departure_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) - - def player_attacking(self, flights: db.TaskForceDict): - assert Embarking in flights and len(flights) == 1, "Invalid flights" - - op = InfantryTransportOperation( - game=self.game, - attacker_name=self.attacker_name, - defender_name=self.defender_name, - from_cp=self.from_cp, - departure_cp=self.departure_cp, - to_cp=self.to_cp - ) - - air_defense = db.find_unittype(AirDefence, self.defender_name)[0] - op.setup(transport=flights[Embarking], - aa={air_defense: 2}) - - self.operation = op diff --git a/game/event/insurgentattack.py b/game/event/insurgentattack.py deleted file mode 100644 index a3ec4f49..00000000 --- a/game/event/insurgentattack.py +++ /dev/null @@ -1,71 +0,0 @@ -import math -import random - -from dcs.task import * - -from game import * -from game.event import * -from game.event.frontlineattack import FrontlineAttackEvent -from game.operation.insurgentattack import InsurgentAttackOperation - -from .event import * - - -class InsurgentAttackEvent(Event): - SUCCESS_FACTOR = 0.7 - TARGET_VARIETY = 2 - TARGET_AMOUNT_FACTOR = 0.5 - STRENGTH_INFLUENCE = 0.1 - - @property - def threat_description(self): - return "" - - @property - def tasks(self): - return [CAS] - - def flight_name(self, for_task: typing.Type[Task]) -> str: - if for_task == CAS: - return "Ground intercept flight" - - def __str__(self): - return "Destroy insurgents" - - def skip(self): - self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) - - def is_successfull(self, debriefing: Debriefing): - - if self.game.player_name == self.attacker_name: - attacker_country = self.game.player_country - else: - attacker_country = self.game.enemy_country - - killed_units = sum([v for k, v in debriefing.destroyed_units[attacker_country].items() if db.unit_task(k) == PinpointStrike]) - all_units = sum(self.targets.values()) - attackers_success = (float(killed_units) / (all_units + 0.01)) > self.SUCCESS_FACTOR - if self.from_cp.captured: - return attackers_success - else: - return not attackers_success - - def player_defending(self, flights: db.TaskForceDict): - assert CAS in flights and len(flights) == 1, "Invalid flights" - - suitable_unittypes = db.find_unittype(Reconnaissance, self.attacker_name) - random.shuffle(suitable_unittypes) - unittypes = suitable_unittypes[:self.TARGET_VARIETY] - typecount = max(math.floor(self.difficulty * self.TARGET_AMOUNT_FACTOR), 1) - self.targets = {unittype: typecount for unittype in unittypes} - - op = InsurgentAttackOperation(game=self.game, - attacker_name=self.attacker_name, - defender_name=self.defender_name, - from_cp=self.from_cp, - departure_cp=self.departure_cp, - to_cp=self.to_cp) - op.setup(target=self.targets, - strikegroup=flights[CAS]) - - self.operation = op diff --git a/game/event/intercept.py b/game/event/intercept.py deleted file mode 100644 index 2b455166..00000000 --- a/game/event/intercept.py +++ /dev/null @@ -1,124 +0,0 @@ -from game.operation.intercept import InterceptOperation - -from .event import * - - -class InterceptEvent(Event): - STRENGTH_INFLUENCE = 0.3 - GLOBAL_STRENGTH_INFLUENCE = 0.3 - AIRDEFENSE_COUNT = 3 - - transport_unit = None # type: FlyingType - - def __init__(self, game, from_cp: ControlPoint, target_cp: ControlPoint, location: Point, attacker_name: str, - defender_name: str): - super().__init__(game, from_cp, target_cp, location, attacker_name, defender_name) - self.location = Conflict.intercept_position(self.from_cp, self.to_cp) - - def __str__(self): - return "Air Intercept" - - @property - def tasks(self): - return [CAP] - - def flight_name(self, for_task: typing.Type[Task]) -> str: - if for_task == CAP: - if self.is_player_attacking: - return "Intercept flight" - else: - return "Escort flight" - - def _enemy_scramble_multiplier(self) -> float: - is_global = self.departure_cp.is_global or self.to_cp.is_global - return self.game.settings.multiplier * is_global and 0.5 or 1 - - @property - def threat_description(self): - return "{} aircraft".format(self.enemy_cp.base.scramble_count(self._enemy_scramble_multiplier(), CAP)) - - @property - def global_cp_available(self) -> bool: - return True - - def is_successfull(self, debriefing: Debriefing): - - if self.game.player_name == self.attacker_name: - defender_country = self.game.enemy_country - else: - defender_country = self.game.player_country - - units_destroyed = debriefing.destroyed_units.get(defender_country, {}).get(self.transport_unit, 0) - if self.from_cp.captured: - return units_destroyed > 0 - else: - return units_destroyed == 0 - - def commit(self, debriefing: Debriefing): - super(InterceptEvent, self).commit(debriefing) - - if self.attacker_name == self.game.player_name: - if self.is_successfull(debriefing): - for _, cp in self.game.theater.conflicts(True): - cp.base.affect_strength(-self.STRENGTH_INFLUENCE) - else: - self.from_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) - else: - # enemy attacking - if self.is_successfull(debriefing): - self.from_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) - else: - self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) - - def skip(self): - if self.to_cp.captured: - self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) - - def player_attacking(self, flights: db.TaskForceDict): - assert CAP in flights and len(flights) == 1, "Invalid flights" - - escort = self.to_cp.base.scramble_sweep(self._enemy_scramble_multiplier()) - - self.transport_unit = random.choice(db.find_unittype(Transport, self.defender_name)) - assert self.transport_unit is not None - - airdefense_unit = db.find_unittype(AirDefence, self.defender_name)[-1] - op = InterceptOperation(game=self.game, - attacker_name=self.attacker_name, - defender_name=self.defender_name, - from_cp=self.from_cp, - departure_cp=self.departure_cp, - to_cp=self.to_cp) - - op.setup(location=self.location, - escort=assigned_units_from(escort), - transport={self.transport_unit: 1}, - airdefense={airdefense_unit: self.AIRDEFENSE_COUNT}, - interceptors=flights[CAP]) - - self.operation = op - - def player_defending(self, flights: db.TaskForceDict): - assert CAP in flights and len(flights) == 1, "Invalid flights" - - interceptors = self.from_cp.base.scramble_interceptors(self.game.settings.multiplier) - - self.transport_unit = random.choice(db.find_unittype(Transport, self.defender_name)) - assert self.transport_unit is not None - - op = InterceptOperation(game=self.game, - attacker_name=self.attacker_name, - defender_name=self.defender_name, - from_cp=self.from_cp, - departure_cp=self.departure_cp, - to_cp=self.to_cp) - - op.setup(location=self.location, - escort=flights[CAP], - transport={self.transport_unit: 1}, - interceptors=assigned_units_from(interceptors), - airdefense={}) - - self.operation = op - - diff --git a/game/event/navalintercept.py b/game/event/navalintercept.py deleted file mode 100644 index 5e0960fb..00000000 --- a/game/event/navalintercept.py +++ /dev/null @@ -1,130 +0,0 @@ -from game.operation.navalintercept import NavalInterceptionOperation - -from .event import * - - -class NavalInterceptEvent(Event): - STRENGTH_INFLUENCE = 0.3 - SUCCESS_RATE = 0.5 - - targets = None # type: db.ShipDict - - def __init__(self, game, from_cp: ControlPoint, target_cp: ControlPoint, location: Point, attacker_name: str, - defender_name: str): - super().__init__(game, from_cp, target_cp, location, attacker_name, defender_name) - self.location = Conflict.naval_intercept_position(from_cp, target_cp, game.theater) - - def _targets_count(self) -> int: - from gen.conflictgen import IMPORTANCE_LOW - factor = (self.to_cp.importance - IMPORTANCE_LOW + 0.1) * 20 - return max(int(factor), 1) - - def __str__(self) -> str: - return "Naval intercept" - - @property - def tasks(self): - if self.is_player_attacking: - return [CAS] - else: - return [CAP] - - def flight_name(self, for_task: typing.Type[Task]) -> str: - if for_task == CAS: - return "Naval intercept flight" - elif for_task == CAP: - return "CAP flight" - - @property - def threat_description(self): - s = "{} ship(s)".format(self._targets_count()) - if not self.departure_cp.captured: - s += ", {} aircraft".format(self.departure_cp.base.scramble_count(self.game.settings.multiplier)) - return s - - @property - def global_cp_available(self) -> bool: - return True - - def is_successfull(self, debriefing: Debriefing): - - if self.game.player_name == self.attacker_name: - defender_country = self.game.enemy_country - else: - defender_country = self.game.player_country - - total_targets = sum(self.targets.values()) - destroyed_targets = 0 - for unit, count in debriefing.destroyed_units.get(defender_country, {}).items(): - if unit in self.targets: - destroyed_targets += count - - if self.departure_cp.captured: - return math.ceil(float(destroyed_targets) / total_targets) > self.SUCCESS_RATE - else: - return math.ceil(float(destroyed_targets) / total_targets) < self.SUCCESS_RATE - - def commit(self, debriefing: Debriefing): - super(NavalInterceptEvent, self).commit(debriefing) - - if self.attacker_name == self.game.player_name: - if self.is_successfull(debriefing): - self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) - else: - self.departure_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) - else: - # enemy attacking - if self.is_successfull(debriefing): - self.departure_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) - else: - self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) - - def skip(self): - if self.to_cp.captured: - self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) - - def player_attacking(self, flights: db.TaskForceDict): - assert CAS in flights and len(flights) == 1, "Invalid flights" - - self.targets = { - random.choice(db.find_unittype(CargoTransportation, self.defender_name)): self._targets_count(), - } - - op = NavalInterceptionOperation( - self.game, - attacker_name=self.attacker_name, - defender_name=self.defender_name, - from_cp=self.from_cp, - departure_cp=self.departure_cp, - to_cp=self.to_cp - ) - - op.setup(location=self.location, - strikegroup=flights[CAS], - interceptors={}, - targets=self.targets) - - self.operation = op - - def player_defending(self, flights: db.TaskForceDict): - assert CAP in flights and len(flights) == 1, "Invalid flights" - - self.targets = { - random.choice(db.find_unittype(CargoTransportation, self.defender_name)): self._targets_count(), - } - - op = NavalInterceptionOperation( - self.game, - attacker_name=self.attacker_name, - defender_name=self.defender_name, - from_cp=self.from_cp, - departure_cp=self.departure_cp, - to_cp=self.to_cp - ) - - strikegroup = self.departure_cp.base.scramble_cas(self.game.settings.multiplier) - op.setup(strikegroup=assigned_units_from(strikegroup), - interceptors=flights[CAP], - targets=self.targets) - - self.operation = op diff --git a/game/event/strike.py b/game/event/strike.py deleted file mode 100644 index 1f6aa3e8..00000000 --- a/game/event/strike.py +++ /dev/null @@ -1,74 +0,0 @@ -from game.operation.strike import StrikeOperation - -from .event import * - - -class StrikeEvent(Event): - STRENGTH_INFLUENCE = 0.0 - SINGLE_OBJECT_STRENGTH_INFLUENCE = 0.05 - - def __str__(self): - return "Strike / SEAD" - - def is_successfull(self, debriefing: Debriefing): - return True - - @property - def threat_description(self): - return "{} aircraft + AA".format(self.to_cp.base.scramble_count(self.game.settings.multiplier, CAP)) - - @property - def tasks(self): - if self.is_player_attacking: - return [CAP, CAS, SEAD] - else: - return [CAP] - - @property - def ai_banned_tasks(self): - return [CAS] - - @property - def player_banned_tasks(self): - return [SEAD] - - @property - def global_cp_available(self) -> bool: - return True - - def flight_name(self, for_task: typing.Type[Task]) -> str: - if for_task == CAP: - if self.is_player_attacking: - return "Escort flight" - else: - return "CAP flight" - elif for_task == SEAD: - return "SEAD flight" - elif for_task == CAS: - return "Strike flight" - - def commit(self, debriefing: Debriefing): - super(StrikeEvent, self).commit(debriefing) - - #self.to_cp.base.affect_strength(-self.SINGLE_OBJECT_STRENGTH_INFLUENCE * len(debriefing.destroyed_objects)) - pass - - def player_attacking(self, flights: db.TaskForceDict): - assert CAP in flights and CAS in flights and SEAD in flights and len(flights) == 3, "Invalid flights" - - op = StrikeOperation( - self.game, - attacker_name=self.attacker_name, - defender_name=self.defender_name, - from_cp=self.from_cp, - departure_cp=self.departure_cp, - to_cp=self.to_cp - ) - - interceptors = self.to_cp.base.scramble_interceptors(self.game.settings.multiplier) - op.setup(strikegroup=flights[CAS], - sead=flights[SEAD], - escort=flights[CAP], - interceptors=assigned_units_from(interceptors)) - - self.operation = op diff --git a/game/factions/china_2000.py b/game/factions/china_2000.py index a3b68ad8..3c4fd3f2 100644 --- a/game/factions/china_2000.py +++ b/game/factions/china_2000.py @@ -10,7 +10,9 @@ China_2000 = { MiG_21Bis, # Standing as J-7 Su_30, + Su_33, J_11A, + JF_17, IL_76MD, IL_78M, @@ -26,14 +28,17 @@ China_2000 = { AirDefence.SAM_SA_6_Kub_LN_2P25, AirDefence.HQ_7_Self_Propelled_LN, - Armor.MBT_T_72B, # Type 99 + Armor.ZTZ_96B, Armor.MBT_T_55, Armor.ZBD_04A, Armor.IFV_BMP_1, + Artillery.MLRS_9A52_Smerch, + Artillery.SPH_2S9_Nona, Unarmed.Transport_Ural_375, Unarmed.Transport_UAZ_469, Infantry.Soldier_AK, + Infantry.Paratrooper_RPG_16, CV_1143_5_Admiral_Kuznetsov, Bulk_cargo_ship_Yakushev, diff --git a/game/factions/france_1995.py b/game/factions/france_1995.py index 406f9b3e..82efb9bc 100644 --- a/game/factions/france_1995.py +++ b/game/factions/france_1995.py @@ -23,6 +23,7 @@ France_1995 = { Unarmed.Transport_M818, Infantry.Infantry_M4, + Infantry.Infantry_M4, AirDefence.SAM_Roland_ADS, AirDefence.SAM_Hawk_PCP, diff --git a/game/factions/russia_1955.py b/game/factions/russia_1955.py index 73fde5e7..efdd464d 100644 --- a/game/factions/russia_1955.py +++ b/game/factions/russia_1955.py @@ -1,6 +1,6 @@ from dcs.planes import MiG_15bis, IL_76MD, IL_78M, An_26B, An_30M, Yak_40 from dcs.ships import CV_1143_5_Admiral_Kuznetsov, Bulk_cargo_ship_Yakushev, Dry_cargo_ship_Ivanov, Tanker_Elnya_160 -from dcs.vehicles import AirDefence, Armor, Unarmed, Infantry +from dcs.vehicles import AirDefence, Armor, Unarmed, Infantry, Artillery Russia_1955 = { "country": "Russia", @@ -16,19 +16,23 @@ Russia_1955 = { AirDefence.AAA_ZU_23_Closed, AirDefence.AAA_ZU_23_on_Ural_375, - Armor.ARV_BRDM_2, Armor.ARV_MTLB_U_BOMAN, Armor.APC_MTLB, Armor.MBT_T_55, + Artillery.MLRS_BM_21_Grad, Unarmed.Transport_Ural_375, Unarmed.Transport_UAZ_469, - Infantry.Soldier_AK, CV_1143_5_Admiral_Kuznetsov, Bulk_cargo_ship_Yakushev, Dry_cargo_ship_Ivanov, - Tanker_Elnya_160 + Tanker_Elnya_160, + + # Infantry squad + Infantry.Paratrooper_AKS, + Infantry.Infantry_Soldier_Rus, + Infantry.Paratrooper_RPG_16, ] } \ No newline at end of file diff --git a/game/factions/russia_1965.py b/game/factions/russia_1965.py index d57a57f8..23abf086 100644 --- a/game/factions/russia_1965.py +++ b/game/factions/russia_1965.py @@ -1,7 +1,7 @@ from dcs.helicopters import Mi_8MT from dcs.planes import MiG_15bis, MiG_19P, MiG_21Bis, IL_76MD, IL_78M, An_26B, An_30M, Yak_40, A_50 from dcs.ships import CV_1143_5_Admiral_Kuznetsov, Bulk_cargo_ship_Yakushev, Dry_cargo_ship_Ivanov, Tanker_Elnya_160 -from dcs.vehicles import AirDefence, Armor, Unarmed, Infantry +from dcs.vehicles import AirDefence, Armor, Unarmed, Infantry, Artillery Russia_1965 = { "country": "Russia", @@ -31,15 +31,21 @@ Russia_1965 = { Armor.IFV_BMD_1, Armor.IFV_BMP_1, Armor.MBT_T_55, + Artillery.MLRS_BM_21_Grad, Unarmed.Transport_Ural_375, Unarmed.Transport_UAZ_469, - Infantry.Soldier_AK, CV_1143_5_Admiral_Kuznetsov, Bulk_cargo_ship_Yakushev, Dry_cargo_ship_Ivanov, - Tanker_Elnya_160 + Tanker_Elnya_160, + + # Infantry squad + Infantry.Paratrooper_AKS, + Infantry.Infantry_Soldier_Rus, + Infantry.Paratrooper_RPG_16, + ], "shorad":[ AirDefence.AAA_ZU_23_Closed diff --git a/game/factions/russia_1975.py b/game/factions/russia_1975.py index 88533eca..6633539d 100644 --- a/game/factions/russia_1975.py +++ b/game/factions/russia_1975.py @@ -2,7 +2,7 @@ from dcs.helicopters import Mi_8MT, Mi_24V from dcs.planes import MiG_21Bis, MiG_23MLD, MiG_25PD, MiG_29A, Su_17M4, Su_24M, Su_25, IL_76MD, IL_78M, An_26B, An_30M, \ Yak_40, A_50 from dcs.ships import * -from dcs.vehicles import AirDefence, Armor, Unarmed, Infantry +from dcs.vehicles import AirDefence, Armor, Unarmed, Infantry, Artillery Russia_1975 = { "country": "Russia", @@ -39,14 +39,22 @@ Russia_1975 = { Armor.IFV_BMP_1, Armor.MBT_T_55, + Artillery.SPH_2S9_Nona, + Artillery.SPH_2S1_Gvozdika, + Unarmed.Transport_Ural_375, Unarmed.Transport_UAZ_469, - Infantry.Soldier_AK, CV_1143_5_Admiral_Kuznetsov, Bulk_cargo_ship_Yakushev, Dry_cargo_ship_Ivanov, - Tanker_Elnya_160 + Tanker_Elnya_160, + + # Infantry squad + Infantry.Paratrooper_AKS, + Infantry.Infantry_Soldier_Rus, + Infantry.Paratrooper_RPG_16, + ], "shorad": [ AirDefence.AAA_ZU_23_Emplacement, diff --git a/game/factions/russia_1990.py b/game/factions/russia_1990.py index ca2421cc..4de94ec1 100644 --- a/game/factions/russia_1990.py +++ b/game/factions/russia_1990.py @@ -16,7 +16,6 @@ Russia_1990 = { Su_27, Su_24M, - Su_24MR, Su_25, IL_76MD, @@ -39,15 +38,21 @@ Russia_1990 = { Armor.IFV_BMD_1, Armor.IFV_BMP_1, Armor.MBT_T_55, + Artillery.MLRS_9K57_Uragan_BM_27, + Artillery.SPH_2S19_Msta, Unarmed.Transport_Ural_375, Unarmed.Transport_UAZ_469, - Infantry.Soldier_AK, CV_1143_5_Admiral_Kuznetsov, Bulk_cargo_ship_Yakushev, Dry_cargo_ship_Ivanov, - Tanker_Elnya_160 + Tanker_Elnya_160, + + # Infantry squad + Infantry.Paratrooper_AKS, + Infantry.Infantry_Soldier_Rus, + Infantry.Paratrooper_RPG_16, ], "shorad":[ AirDefence.SAM_SA_9_Strela_1_9P31, diff --git a/game/factions/russia_2010.py b/game/factions/russia_2010.py index d6abc1e0..3c6aa11f 100644 --- a/game/factions/russia_2010.py +++ b/game/factions/russia_2010.py @@ -16,7 +16,7 @@ Russia_2010 = { Su_25T, Su_34, - Su_24MR, + Su_24M, L_39ZA, IL_76MD, @@ -38,13 +38,21 @@ Russia_2010 = { Armor.MBT_T_80U, Armor.MBT_T_72B, + Artillery.MLRS_9K57_Uragan_BM_27, + Artillery.SPH_2S19_Msta, + Unarmed.Transport_Ural_375, Unarmed.Transport_UAZ_469, - Infantry.Soldier_AK, + CV_1143_5_Admiral_Kuznetsov, Bulk_cargo_ship_Yakushev, Dry_cargo_ship_Ivanov, Tanker_Elnya_160, + + # Infantry squad + Infantry.Paratrooper_AKS, + Infantry.Infantry_Soldier_Rus, + Infantry.Paratrooper_RPG_16, ], "shorad":[ AirDefence.SAM_SA_19_Tunguska_2S6, diff --git a/game/factions/usa_1944.py b/game/factions/usa_1944.py index abb0707d..9a03c997 100644 --- a/game/factions/usa_1944.py +++ b/game/factions/usa_1944.py @@ -15,6 +15,8 @@ USA_1944 = { Armor.MT_M4A4_Sherman_Firefly, Armor.M30_Cargo_Carrier, Armor.APC_M2A1, + Armor.CT_Cromwell_IV, + Artillery.M12_GMC, Infantry.Infantry_M1_Garand, diff --git a/game/factions/usa_1960.py b/game/factions/usa_1960.py index 57d4a996..8ec3fb37 100644 --- a/game/factions/usa_1960.py +++ b/game/factions/usa_1960.py @@ -22,6 +22,7 @@ USA_1960 = { Unarmed.Transport_M818, Infantry.Infantry_M4, + Infantry.Soldier_M249, AirDefence.AAA_Vulcan_M163, diff --git a/game/factions/usa_1965.py b/game/factions/usa_1965.py index 177759b6..23a34495 100644 --- a/game/factions/usa_1965.py +++ b/game/factions/usa_1965.py @@ -24,6 +24,7 @@ USA_1965 = { Armor.APC_M113, Unarmed.Transport_M818, Infantry.Infantry_M4, + Infantry.Soldier_M249, AirDefence.SAM_Chaparral_M48, AirDefence.SAM_Hawk_PCP, diff --git a/game/factions/usa_1990.py b/game/factions/usa_1990.py index c30ac322..9b1a97c2 100644 --- a/game/factions/usa_1990.py +++ b/game/factions/usa_1990.py @@ -31,6 +31,7 @@ USA_1990 = { Armor.ATGM_M1134_Stryker, Unarmed.Transport_M818, Infantry.Infantry_M4, + Infantry.Soldier_M249, AirDefence.SAM_Hawk_PCP, diff --git a/game/factions/usa_2005.py b/game/factions/usa_2005.py index 051b17ee..dde1a4f6 100644 --- a/game/factions/usa_2005.py +++ b/game/factions/usa_2005.py @@ -11,6 +11,7 @@ USA_2005 = { F_14B, FA_18C_hornet, F_16C_50, + JF_17, A_10C, AV8BNA, @@ -32,8 +33,12 @@ USA_2005 = { Armor.APC_M1043_HMMWV_Armament, Armor.ATGM_M1045_HMMWV_TOW, + Artillery.MLRS_M270, + Artillery.SPH_M109_Paladin, + Unarmed.Transport_M818, Infantry.Infantry_M4, + Infantry.Soldier_M249, AirDefence.SAM_Hawk_PCP, AirDefence.SAM_Patriot_EPP_III, diff --git a/game/game.py b/game/game.py index 423bdc68..776d26e6 100644 --- a/game/game.py +++ b/game/game.py @@ -9,6 +9,7 @@ from dcs.vehicles import * from game.game_stats import GameStats from gen.conflictgen import Conflict from gen.flights.ai_flight_planner import FlightPlanner +from gen.ground_forces.ai_ground_planner import GroundPlanner from userdata.debriefing import Debriefing from theater import * @@ -38,39 +39,6 @@ PLAYER_INTERCEPT_GLOBAL_PROBABILITY_BASE = 30 PLAYER_INTERCEPT_GLOBAL_PROBABILITY_LOG = 2 PLAYER_BASEATTACK_THRESHOLD = 0.4 -""" -Various events probabilities. First key is player probabilty, second is enemy probability. -For the enemy events, only 1 event of each type could be generated for a turn. - -Events: -* BaseAttackEvent - capture base -* InterceptEvent - air intercept -* FrontlineAttackEvent - frontline attack -* NavalInterceptEvent - naval intercept -* StrikeEvent - strike event -* InfantryTransportEvent - helicopter infantry transport -""" -EVENT_PROBABILITIES = { - # events always present; only for the player - FrontlineAttackEvent: [100, 9], - #FrontlinePatrolEvent: [100, 0], - StrikeEvent: [100, 0], - - # events randomly present; only for the player - #InfantryTransportEvent: [25, 0], - ConvoyStrikeEvent: [25, 0], - - # events conditionally present; for both enemy and player - BaseAttackEvent: [100, 9], - - # events randomly present; for both enemy and player - InterceptEvent: [25, 9], - NavalInterceptEvent: [25, 9], - - # events randomly present; only for the enemy - InsurgentAttackEvent: [0, 6], -} - # amount of strength player bases recover for the turn PLAYER_BASE_STRENGTH_RECOVERY = 0.2 @@ -81,9 +49,11 @@ ENEMY_BASE_STRENGTH_RECOVERY = 0.05 AWACS_BUDGET_COST = 4 # Initial budget value -PLAYER_BUDGET_INITIAL = 170 +PLAYER_BUDGET_INITIAL = 450 + # Base post-turn bonus value -PLAYER_BUDGET_BASE = 14 +PLAYER_BUDGET_BASE = 10 + # Bonus multiplier logarithm base PLAYER_BUDGET_IMPORTANCE_LOG = 2 @@ -113,6 +83,7 @@ class Game: self.game_stats = GameStats() self.game_stats.update(self) self.planners = {} + self.ground_planners = {} def _roll(self, prob, mult): if self.settings.version == "dev": @@ -122,88 +93,11 @@ class Game: return random.randint(1, 100) <= prob * mult def _generate_player_event(self, event_class, player_cp, enemy_cp): - if event_class == NavalInterceptEvent and enemy_cp.radials == LAND: - # skip naval events for non-coastal CPs - return - - if event_class == BaseAttackEvent and enemy_cp.base.strength > PLAYER_BASEATTACK_THRESHOLD and self.settings.version != "dev": - # skip base attack events for CPs yet too strong - return - - if event_class == StrikeEvent and not enemy_cp.ground_objects: - # skip strikes in case of no targets - return - self.events.append(event_class(self, player_cp, enemy_cp, enemy_cp.position, self.player_name, self.enemy_name)) - def _generate_enemy_event(self, event_class, player_cp, enemy_cp): - if event_class in [type(x) for x in self.events if not self.is_player_attack(x)]: - # skip already generated enemy event types - return - - if player_cp in self.ignored_cps: - # skip attacks against ignored CPs (for example just captured ones) - return - - if enemy_cp.base.total_planes == 0: - # skip event if there's no planes on the base - return - - if player_cp.is_global: - # skip carriers - return - - if event_class == NavalInterceptEvent: - if player_cp.radials == LAND: - # skip naval events for non-coastal CPs - return - elif event_class == StrikeEvent: - if not player_cp.ground_objects: - # skip strikes if there's no ground objects - return - elif event_class == BaseAttackEvent: - if BaseAttackEvent in [type(x) for x in self.events]: - # skip base attack event if there's another one going on - return - - if enemy_cp.base.total_armor == 0: - # skip base attack if there's no armor - return - - if player_cp.base.strength > PLAYER_BASEATTACK_THRESHOLD: - # skip base attack if strength is too high - return - - self.events.append(event_class(self, enemy_cp, player_cp, player_cp.position, self.enemy_name, self.player_name)) - def _generate_events(self): - strikes_generated_for = set() - base_attack_generated_for = set() - for player_cp, enemy_cp in self.theater.conflicts(True): - for event_class, (player_probability, enemy_probability) in EVENT_PROBABILITIES.items(): - if event_class in [FrontlineAttackEvent, FrontlinePatrolEvent, InfantryTransportEvent, ConvoyStrikeEvent]: - # skip events requiring frontline - if not Conflict.has_frontline_between(player_cp, enemy_cp): - continue - - # don't generate multiple 100% events from each attack direction - if event_class is StrikeEvent: - if enemy_cp in strikes_generated_for: - continue - if event_class is BaseAttackEvent: - if enemy_cp in base_attack_generated_for: - continue - - if player_probability == 100 or player_probability > 0 and self._roll(player_probability, player_cp.base.strength): - self._generate_player_event(event_class, player_cp, enemy_cp) - if event_class is StrikeEvent: - strikes_generated_for.add(enemy_cp) - if event_class is BaseAttackEvent: - base_attack_generated_for.add(enemy_cp) - - if enemy_probability == 100 or enemy_probability > 0 and self._roll(enemy_probability, enemy_cp.base.strength): - self._generate_enemy_event(event_class, player_cp, enemy_cp) + self._generate_player_event(FrontlineAttackEvent, player_cp, enemy_cp) def commision_unit_types(self, cp: ControlPoint, for_task: Task) -> typing.Collection[UnitType]: importance_factor = (cp.importance - IMPORTANCE_LOW) / (IMPORTANCE_HIGH - IMPORTANCE_LOW) @@ -214,7 +108,7 @@ class Game: return db.choose_units(for_task, importance_factor, COMMISION_UNIT_VARIETY, self.enemy_name) def _commision_units(self, cp: ControlPoint): - for for_task in [PinpointStrike, CAS, CAP, AirDefence]: + for for_task in [CAS, CAP, AirDefence]: limit = COMMISION_LIMITS_FACTORS[for_task] * math.pow(cp.importance, COMMISION_LIMITS_SCALE) * self.settings.multiplier missing_units = limit - cp.base.total_units(for_task) if missing_units > 0: @@ -229,11 +123,32 @@ class Game: @property def budget_reward_amount(self): + reward = 0 if len(self.theater.player_points()) > 0: - total_importance = sum([x.importance * x.base.strength for x in self.theater.player_points()]) - return math.ceil(math.log(total_importance + 1, PLAYER_BUDGET_IMPORTANCE_LOG) * PLAYER_BUDGET_BASE * self.settings.multiplier) + reward = PLAYER_BUDGET_BASE * len(self.theater.player_points()) + for cp in self.theater.player_points(): + for g in cp.ground_objects: + if g.category == "power": + reward = reward + 10 + elif g.category == "warehouse": + reward = reward + 8 + elif g.category == "fuel": + reward = reward + 10 + elif g.category == "ammo": + reward = reward + 6 + elif g.category == "farp": + reward = reward + 4 + elif g.category == "fob": + reward = reward + 4 + elif g.category == "factory": + reward = reward + 25 + elif g.category == "comms": + reward = reward + 25 + elif g.category == "oil": + reward = reward + 45 + return reward else: - return 0 + return reward def _budget_player(self): self.budget += self.budget_reward_amount @@ -310,14 +225,21 @@ class Game: # Update statistics self.game_stats.update(self) - # Plan flights for next turn + # Plan flights & combat for next turn self.planners = {} + self.ground_planners = {} for cp in self.theater.controlpoints: if cp.has_runway(): planner = FlightPlanner(cp, self) planner.plan_flights() self.planners[cp.id] = planner + if cp.has_frontline: + gplanner = GroundPlanner(cp, self) + gplanner.plan_groundwar() + self.ground_planners[cp.id] = gplanner + + @property def current_turn_daytime(self): diff --git a/game/operation/baseattack.py b/game/operation/baseattack.py deleted file mode 100644 index d57cb519..00000000 --- a/game/operation/baseattack.py +++ /dev/null @@ -1,70 +0,0 @@ -from game.db import assigned_units_split - -from gen.triggergen import * - -from .operation import * - - -class BaseAttackOperation(Operation): - cas = None # type: db.AssignedUnitsDict - escort = None # type: db.AssignedUnitsDict - intercept = None # type: db.AssignedUnitsDict - attack = None # type: db.ArmorDict - defense = None # type: db.ArmorDict - aa = None # type: db.AirDefenseDict - - trigger_radius = TRIGGER_RADIUS_SMALL - - def setup(self, - cas: db.AssignedUnitsDict, - escort: db.AssignedUnitsDict, - attack: db.AssignedUnitsDict, - intercept: db.AssignedUnitsDict, - defense: db.ArmorDict, - aa: db.AirDefenseDict): - self.cas = cas - self.escort = escort - self.intercept = intercept - self.attack = attack - self.defense = defense - self.aa = aa - - def prepare(self, terrain: dcs.terrain.Terrain, is_quick: bool): - super(BaseAttackOperation, self).prepare(terrain, is_quick) - - self.defenders_starting_position = None - if self.game.player_name == self.defender_name: - self.attackers_starting_position = None - - conflict = Conflict.capture_conflict( - attacker_name=self.attacker_name, - defender_name=self.defender_name, - attacker=self.current_mission.country(self.attacker_country), - defender=self.current_mission.country(self.defender_country), - from_cp=self.from_cp, - to_cp=self.to_cp, - theater=self.game.theater - ) - self.initialize(mission=self.current_mission, - conflict=conflict) - - def generate(self): - self.armorgen.generate(self.attack, self.defense) - - self.airgen.generate_defense(*assigned_units_split(self.intercept), at=self.defenders_starting_position) - - self.airgen.generate_cas_strikegroup(*assigned_units_split(self.cas), at=self.attackers_starting_position) - self.airgen.generate_attackers_escort(*assigned_units_split(self.escort), at=self.attackers_starting_position) - - self.visualgen.generate_target_smokes(self.to_cp) - - self.briefinggen.title = "Base attack" - self.briefinggen.description = "The goal of an attacker is to lower defender presence by destroying their armor and aircraft. Base will be considered captured if attackers on the ground overrun the defenders. Be advised that your flight will not attack anything until you explicitly tell them so by comms menu." - - if self.game.player_name == self.attacker_name: - self.briefinggen.append_waypoint("TARGET") - else: - pass - - super(BaseAttackOperation, self).generate() - diff --git a/game/operation/convoystrike.py b/game/operation/convoystrike.py deleted file mode 100644 index 835cca15..00000000 --- a/game/operation/convoystrike.py +++ /dev/null @@ -1,51 +0,0 @@ -from game.db import assigned_units_split - -from .operation import * - - -class ConvoyStrikeOperation(Operation): - strikegroup = None # type: db.AssignedUnitsDict - target = None # type: db.ArmorDict - - def setup(self, - target: db.ArmorDict, - strikegroup: db.AssignedUnitsDict): - self.strikegroup = strikegroup - self.target = target - - def prepare(self, terrain: Terrain, is_quick: bool): - super(ConvoyStrikeOperation, self).prepare(terrain, is_quick) - - conflict = Conflict.convoy_strike_conflict( - attacker_name=self.attacker_name, - defender_name=self.defender_name, - attacker=self.current_mission.country(self.attacker_country), - defender=self.current_mission.country(self.defender_country), - from_cp=self.from_cp, - to_cp=self.to_cp, - theater=self.game.theater - ) - - self.initialize(mission=self.current_mission, - conflict=conflict) - - def generate(self): - if self.is_player_attack: - self.prepare_carriers(db.unitdict_from(self.strikegroup)) - - planes_flights = {k: v for k, v in self.strikegroup.items() if k in plane_map.values()} - self.airgen.generate_cas_strikegroup(*assigned_units_split(planes_flights), at=self.attackers_starting_position) - - heli_flights = {k: v for k, v in self.strikegroup.items() if k in helicopters.helicopter_map.values()} - if heli_flights: - self.briefinggen.append_frequency("FARP + Heli flights", "127.5 MHz AM") - for farp, dict in zip(self.groundobjectgen.generate_farps(sum([x[0] for x in heli_flights.values()])), - db.assignedunits_split_to_count(heli_flights, self.groundobjectgen.FARP_CAPACITY)): - self.airgen.generate_cas_strikegroup(*assigned_units_split(dict), - at=farp, - escort=len(planes_flights) == 0) - - self.armorgen.generate_convoy(self.target) - - self.briefinggen.append_waypoint("TARGET") - super(ConvoyStrikeOperation, self).generate() diff --git a/game/operation/frontlinepatrol.py b/game/operation/frontlinepatrol.py deleted file mode 100644 index cf64d2cb..00000000 --- a/game/operation/frontlinepatrol.py +++ /dev/null @@ -1,61 +0,0 @@ -from game.db import assigned_units_split - -from .operation import * - - -MAX_DISTANCE_BETWEEN_GROUPS = 12000 - - -class FrontlinePatrolOperation(Operation): - cas = None # type: db.AssignedUnitsDict - escort = None # type: db.AssignedUnitsDict - interceptors = None # type: db.AssignedUnitsDict - - armor_attackers = None # type: db.ArmorDict - armor_defenders = None # type: db.ArmorDict - - def setup(self, - cas: db.AssignedUnitsDict, - escort: db.AssignedUnitsDict, - interceptors: db.AssignedUnitsDict, - armor_attackers: db.ArmorDict, - armor_defenders: db.ArmorDict): - self.cas = cas - self.escort = escort - self.interceptors = interceptors - - self.armor_attackers = armor_attackers - self.armor_defenders = armor_defenders - - def prepare(self, terrain: Terrain, is_quick: bool): - super(FrontlinePatrolOperation, self).prepare(terrain, is_quick) - self.defenders_starting_position = None - - conflict = Conflict.frontline_cap_conflict( - attacker_name=self.attacker_name, - defender_name=self.defender_name, - attacker=self.current_mission.country(self.attacker_country), - defender=self.current_mission.country(self.defender_country), - from_cp=self.from_cp, - to_cp=self.to_cp, - theater=self.game.theater - ) - - self.initialize(mission=self.current_mission, - conflict=conflict) - - def generate(self): - if self.is_player_attack: - self.prepare_carriers(db.unitdict_from(self.interceptors)) - - self.airgen.generate_defenders_cas(*assigned_units_split(self.cas), at=self.defenders_starting_position) - self.airgen.generate_defenders_escort(*assigned_units_split(self.escort), at=self.defenders_starting_position) - self.airgen.generate_migcap(*assigned_units_split(self.interceptors), at=self.attackers_starting_position) - - self.armorgen.generate_vec(self.armor_attackers, self.armor_defenders) - - self.briefinggen.title = "Frontline CAP" - self.briefinggen.description = "Providing CAP support for ground units attacking enemy lines. Enemy will scramble its CAS and your task is to intercept it. Operation will be considered successful if total number of friendly units will be lower than enemy by at least a factor of 0.8 (i.e. with 12 units from both sides, there should be at least 8 friendly units alive), lowering targets strength as a result." - self.briefinggen.append_waypoint("CAP AREA IP") - self.briefinggen.append_waypoint("CAP AREA EGRESS") - super(FrontlinePatrolOperation, self).generate() diff --git a/game/operation/infantrytransport.py b/game/operation/infantrytransport.py deleted file mode 100644 index 756722b3..00000000 --- a/game/operation/infantrytransport.py +++ /dev/null @@ -1,47 +0,0 @@ -from game.db import assigned_units_split - -from .operation import * - - -class InfantryTransportOperation(Operation): - transport = None # type: db.AssignedUnitsDict - aa = None # type: db.AirDefenseDict - - def setup(self, transport: db.AssignedUnitsDict, aa: db.AirDefenseDict): - self.transport = transport - self.aa = aa - - def prepare(self, terrain: Terrain, is_quick: bool): - super(InfantryTransportOperation, self).prepare(terrain, is_quick) - - conflict = Conflict.transport_conflict( - attacker_name=self.attacker_name, - defender_name=self.defender_name, - attacker=self.current_mission.country(self.attacker_country), - defender=self.current_mission.country(self.defender_country), - from_cp=self.from_cp, - to_cp=self.to_cp, - theater=self.game.theater - ) - - self.initialize(mission=self.current_mission, - conflict=conflict) - - def generate(self): - self.airgen.generate_passenger_transport(*assigned_units_split(self.transport), at=self.attackers_starting_position) - - self.armorgen.generate_passengers(count=6) - - self.visualgen.generate_transportation_marker(self.conflict.ground_attackers_location) - self.visualgen.generate_transportation_destination(self.conflict.position) - - self.briefinggen.title = "Infantry transport" - self.briefinggen.description = "Helicopter operation to transport infantry troops from the base to the front line. Lowers target strength" - self.briefinggen.append_waypoint("DROP POINT") - - # TODO: horrible, horrible hack - # this will disable vehicle activation triggers, - # which aren't needed on this type of missions - self.is_quick = True - super(InfantryTransportOperation, self).generate() - self.is_quick = False diff --git a/game/operation/insurgentattack.py b/game/operation/insurgentattack.py deleted file mode 100644 index 2e81e831..00000000 --- a/game/operation/insurgentattack.py +++ /dev/null @@ -1,40 +0,0 @@ -from game.db import assigned_units_split - -from .operation import * - - -class InsurgentAttackOperation(Operation): - strikegroup = None # type: db.AssignedUnitsDict - target = None # type: db.ArmorDict - - def setup(self, - target: db.ArmorDict, - strikegroup: db.AssignedUnitsDict): - self.strikegroup = strikegroup - self.target = target - - def prepare(self, terrain: Terrain, is_quick: bool): - super(InsurgentAttackOperation, self).prepare(terrain, is_quick) - - conflict = Conflict.ground_attack_conflict( - attacker_name=self.attacker_name, - defender_name=self.defender_name, - attacker=self.current_mission.country(self.attacker_country), - defender=self.current_mission.country(self.defender_country), - from_cp=self.from_cp, - to_cp=self.to_cp, - theater=self.game.theater - ) - - self.initialize(mission=self.current_mission, - conflict=conflict) - - def generate(self): - self.airgen.generate_defenders_cas(*assigned_units_split(self.strikegroup), at=self.defenders_starting_position) - self.armorgen.generate(self.target, {}) - - self.briefinggen.title = "Destroy insurgents" - self.briefinggen.description = "Destroy vehicles of insurgents in close proximity of the friendly base. Be advised that your flight will not attack anything until you explicitly tell them so by comms menu." - self.briefinggen.append_waypoint("TARGET") - - super(InsurgentAttackOperation, self).generate() diff --git a/game/operation/intercept.py b/game/operation/intercept.py deleted file mode 100644 index 60d5afd0..00000000 --- a/game/operation/intercept.py +++ /dev/null @@ -1,67 +0,0 @@ -from game.db import assigned_units_split - -from .operation import * - - -class InterceptOperation(Operation): - location = None # type: Point - escort = None # type: db.AssignedUnitsDict - transport = None # type: db.PlaneDict - interceptors = None # type: db.AssignedUnitsDict - airdefense = None # type: db.AirDefenseDict - - trigger_radius = TRIGGER_RADIUS_LARGE - - def setup(self, - location: Point, - escort: db.AssignedUnitsDict, - transport: db.PlaneDict, - airdefense: db.AirDefenseDict, - interceptors: db.AssignedUnitsDict): - self.location = location - self.escort = escort - self.transport = transport - self.airdefense = airdefense - self.interceptors = interceptors - - def prepare(self, terrain: Terrain, is_quick: bool): - super(InterceptOperation, self).prepare(terrain, is_quick) - self.defenders_starting_position = None - if self.defender_name == self.game.player_name: - self.attackers_starting_position = None - - conflict = Conflict.intercept_conflict( - attacker_name=self.attacker_name, - defender_name=self.defender_name, - attacker=self.current_mission.country(self.attacker_country), - defender=self.current_mission.country(self.defender_country), - position=self.location, - from_cp=self.from_cp, - to_cp=self.to_cp, - theater=self.game.theater - ) - - self.initialize(mission=self.current_mission, - conflict=conflict) - - def generate(self): - if self.is_player_attack: - self.prepare_carriers(db.unitdict_from(self.interceptors)) - - self.airgen.generate_transport(self.transport, self.to_cp.at) - self.airgen.generate_defenders_escort(*assigned_units_split(self.escort), at=self.defenders_starting_position) - - self.airgen.generate_interception(*assigned_units_split(self.interceptors), at=self.attackers_starting_position) - - self.briefinggen.title = "Air Intercept" - - if self.game.player_name == self.attacker_name: - self.briefinggen.description = "Intercept enemy supply transport aircraft. Escort will also be present if there are available planes on the base. Operation will be considered successful if most of the targets are destroyed, lowering targets strength as a result" - self.briefinggen.append_waypoint("TARGET") - for unit_type, count in self.transport.items(): - self.briefinggen.append_target("{} ({})".format(db.unit_type_name(unit_type), count)) - else: - self.briefinggen.description = "Escort friendly supply transport aircraft. Operation will be considered failed if most of the targets are destroyed, lowering CP strength as a result" - - super(InterceptOperation, self).generate() - diff --git a/game/operation/navalintercept.py b/game/operation/navalintercept.py deleted file mode 100644 index c4144c1a..00000000 --- a/game/operation/navalintercept.py +++ /dev/null @@ -1,69 +0,0 @@ -from game.db import assigned_units_split - -from .operation import * - - -class NavalInterceptionOperation(Operation): - location = None # type: Point - strikegroup = None # type: db.AssignedUnitsDict - interceptors = None # type: db.AssignedUnitsDict - targets = None # type: db.ShipDict - trigger_radius = TRIGGER_RADIUS_LARGE - - def setup(self, - location: Point, - strikegroup: db.AssignedUnitsDict, - interceptors: db.AssignedUnitsDict, - targets: db.ShipDict): - self.location = location - self.strikegroup = strikegroup - self.interceptors = interceptors - self.targets = targets - - def prepare(self, terrain: Terrain, is_quick: bool): - super(NavalInterceptionOperation, self).prepare(terrain, is_quick) - if self.defender_name == self.game.player_name: - self.attackers_starting_position = None - - conflict = Conflict.naval_intercept_conflict( - attacker_name=self.attacker_name, - defender_name=self.defender_name, - attacker=self.current_mission.country(self.attacker_country), - defender=self.current_mission.country(self.defender_country), - position=self.location, - from_cp=self.from_cp, - to_cp=self.to_cp, - theater=self.game.theater - ) - - self.initialize(self.current_mission, conflict) - - def generate(self): - if self.is_player_attack: - self.prepare_carriers(db.unitdict_from(self.strikegroup)) - - target_groups = self.shipgen.generate_cargo(units=self.targets) - - self.airgen.generate_ship_strikegroup( - *assigned_units_split(self.strikegroup), - target_groups=target_groups, - at=self.attackers_starting_position - ) - - if self.interceptors: - self.airgen.generate_defense( - *assigned_units_split(self.interceptors), - at=self.defenders_starting_position - ) - - self.briefinggen.title = "Naval Intercept" - if self.game.player_name == self.attacker_name: - self.briefinggen.description = "Destroy supply transport ships. Lowers target strength. Be advised that your flight will not attack anything until you explicitly tell them so by comms menu." - for unit_type, count in self.targets.items(): - self.briefinggen.append_target("{} ({})".format(db.unit_type_name(unit_type), count)) - else: - self.briefinggen.description = "Protect supply transport ships." - self.briefinggen.append_waypoint("TARGET") - - super(NavalInterceptionOperation, self).generate() - diff --git a/game/operation/operation.py b/game/operation/operation.py index ae9524b8..444fd8d2 100644 --- a/game/operation/operation.py +++ b/game/operation/operation.py @@ -65,7 +65,6 @@ class Operation: def initialize(self, mission: Mission, conflict: Conflict): self.current_mission = mission self.conflict = conflict - self.armorgen = ArmorConflictGenerator(mission, conflict) self.airgen = AircraftConflictGenerator(mission, conflict, self.game.settings) self.shipgen = ShipGenerator(mission, conflict) self.airsupportgen = AirSupportConflictGenerator(mission, conflict, self.game) @@ -158,10 +157,13 @@ class Operation: self.current_mission.country(self.attacker_country), self.current_mission.country(self.defender_country), player_cp, enemy_cp, self.game.theater) - armorgen = ArmorConflictGenerator(self.current_mission, conflict) - armorgen.generate_vec(player_cp.base.armor, enemy_cp.base.armor) + # Generate frontline ops + player_gp = self.game.ground_planners[player_cp.id].units_per_cp[enemy_cp.id] + enemy_gp = self.game.ground_planners[enemy_cp.id].units_per_cp[player_cp.id] + groundConflictGen = GroundConflictGenerator(self.current_mission, conflict, self.game, player_gp, enemy_gp, player_cp.stances[enemy_cp.id]) + groundConflictGen.generate() - #Setup combined arms parameters + # Setup combined arms parameters self.current_mission.groundControl.pilot_can_control_vehicles = self.ca_slots > 0 if self.game.player_country in [country.name for country in self.current_mission.coalition["blue"].countries.values()]: self.current_mission.groundControl.blue_tactical_commander = self.ca_slots diff --git a/game/operation/strike.py b/game/operation/strike.py deleted file mode 100644 index cabf2a77..00000000 --- a/game/operation/strike.py +++ /dev/null @@ -1,96 +0,0 @@ -from game.db import assigned_units_split - -from .operation import * - - -class StrikeOperation(Operation): - strikegroup = None # type: db.AssignedUnitsDict - sead = None # type: db.AssignedUnitsDict - escort = None # type: db.AssignedUnitsDict - interceptors = None # type: db.AssignedUnitsDict - - trigger_radius = TRIGGER_RADIUS_ALL_MAP - - def setup(self, - strikegroup: db.AssignedUnitsDict, - sead: db.AssignedUnitsDict, - escort: db.AssignedUnitsDict, - interceptors: db.AssignedUnitsDict): - self.strikegroup = strikegroup - self.sead = sead - self.escort = escort - self.interceptors = interceptors - - def prepare(self, terrain: Terrain, is_quick: bool): - super(StrikeOperation, self).prepare(terrain, is_quick) - - self.defenders_starting_position = None - if self.game.player_name == self.defender_name: - self.attackers_starting_position = None - - conflict = Conflict.strike_conflict( - attacker_name=self.attacker_name, - defender_name=self.defender_name, - attacker=self.current_mission.country(self.attacker_country), - defender=self.current_mission.country(self.defender_country), - from_cp=self.from_cp, - to_cp=self.to_cp, - theater=self.game.theater - ) - - self.initialize(mission=self.current_mission, - conflict=conflict) - - def generate(self): - self.prepare_carriers(db.unitdict_merge(db.unitdict_from(self.strikegroup), db.unitdict_from(self.escort))) - - targets = [] # type: typing.List[typing.Tuple[str, str, Point]] - sead_targets = [] # type: typing.List[typing.Tuple[str, str, Point]] - category_counters = {} # type: typing.Dict[str, int] - processed_groups = [] - - for object in self.to_cp.ground_objects: - if object.group_identifier in processed_groups: - continue - - processed_groups.append(object.group_identifier) - category_counters[object.category] = category_counters.get(object.category, 0) + 1 - markpoint_name = "{}{}".format(object.name_abbrev, category_counters[object.category]) - - if object.category == "aa": - sead_targets.append((str(object), markpoint_name, object.position)) - - targets.append((str(object), markpoint_name, object.position)) - - targets.sort(key=lambda x: self.from_cp.position.distance_to_point(x[2])) - - for (name, markpoint_name, _) in targets: - self.briefinggen.append_waypoint("TARGET {} (TP {})".format(str(name), markpoint_name)) - - planes_flights = {k: v for k, v in self.strikegroup.items() if k in plane_map.values()} - self.airgen.generate_ground_attack_strikegroup(*assigned_units_split(planes_flights), - targets=[(mp, pos) for (n, mp, pos) in targets], - at=self.attackers_starting_position, - escort=len(self.sead) == 0) - - self.airgen.generate_sead_strikegroup(*assigned_units_split(self.sead), - targets=[(mp, pos) for (n, mp, pos) in sead_targets], - at=self.attackers_starting_position, - escort=len(self.sead) > 0) - - heli_flights = {k: v for k, v in self.strikegroup.items() if k in helicopters.helicopter_map.values()} - if heli_flights: - self.briefinggen.append_frequency("FARP", "127.5 MHz AM") - for farp, dict in zip(self.groundobjectgen.generate_farps(sum([x[0] for x in heli_flights.values()])), - db.assignedunits_split_to_count(heli_flights, self.groundobjectgen.FARP_CAPACITY)): - self.airgen.generate_ground_attack_strikegroup(*assigned_units_split(dict), - targets=[(mp, pos) for (n, mp, pos) in targets], - at=farp, - escort=len(planes_flights) == 0) - - self.airgen.generate_attackers_escort(*assigned_units_split(self.escort), at=self.attackers_starting_position) - self.airgen.generate_barcap(*assigned_units_split(self.interceptors), at=self.defenders_starting_position) - - self.briefinggen.title = "Strike" - self.briefinggen.description = "Destroy infrastructure assets and military supplies in the region. Each building destroyed will lower targets strength." - super(StrikeOperation, self).generate() diff --git a/gen/armor.py b/gen/armor.py index 354b3f24..41dbafdb 100644 --- a/gen/armor.py +++ b/gen/armor.py @@ -1,17 +1,8 @@ -import logging - -from random import randint -from itertools import zip_longest - -from game import db -from .conflictgen import * -from .naming import * - -from dcs.mission import * -from dcs.unittype import * -from dcs.point import * from dcs.task import * -from dcs.country import * + +from gen import namegen +from gen.ground_forces.ai_ground_planner import CombatGroupRole, DISTANCE_FROM_FRONTLINE +from .conflictgen import * SPREAD_DISTANCE_FACTOR = 0.1, 0.3 SPREAD_DISTANCE_SIZE_FACTOR = 0.1 @@ -20,123 +11,306 @@ FRONTLINE_CAS_FIGHTS_COUNT = 16, 24 FRONTLINE_CAS_GROUP_MIN = 1, 2 FRONTLINE_CAS_PADDING = 12000 +RETREAT_DISTANCE = 20000 +BREAKTHROUGH_OFFENSIVE_DISTANCE = 35000 +AGGRESIVE_MOVE_DISTANCE = 16000 + FIGHT_DISTANCE = 3500 +class GroundConflictGenerator: -class ArmorConflictGenerator: - - def __init__(self, mission: Mission, conflict: Conflict): + def __init__(self, mission: Mission, conflict: Conflict, game, player_planned_combat_groups, enemy_planned_combat_groups, player_stance): self.m = mission self.conflict = conflict + self.enemy_planned_combat_groups = enemy_planned_combat_groups + self.player_planned_combat_groups = player_planned_combat_groups + self.player_stance = CombatStance(player_stance) + self.enemy_stance = CombatStance.AGGRESIVE if len(enemy_planned_combat_groups) > len(player_planned_combat_groups) else CombatStance.DEFENSIVE + self.game = game def _group_point(self, point) -> Point: distance = randint( int(self.conflict.size * SPREAD_DISTANCE_FACTOR[0]), int(self.conflict.size * SPREAD_DISTANCE_FACTOR[1]), ) - return point.random_point_within(distance, self.conflict.size * SPREAD_DISTANCE_SIZE_FACTOR) - def _generate_group(self, side: Country, unit: VehicleType, count: int, at: Point, to: Point = None, move_formation: PointAction = PointAction.OffRoad): + def generate(self): + + player_groups = [] + enemy_groups = [] + + combat_width = self.conflict.distance/2 + if combat_width > 500000: + combat_width = 500000 + if combat_width < 35000: + combat_width = 35000 + + position = Conflict.frontline_position(self.game.theater, self.conflict.from_cp, self.conflict.to_cp) + + # Create player groups at random position + for group in self.player_planned_combat_groups: + if group.role == CombatGroupRole.ARTILLERY: + distance_from_frontline = self.get_artilery_group_distance_from_frontline(group) + else: + distance_from_frontline = DISTANCE_FROM_FRONTLINE[group.role] + final_position = self.get_valid_position_for_group(position, True, combat_width, distance_from_frontline) + + if final_position is not None: + g = self._generate_group( + side=self.m.country(self.game.player_country), + unit=group.units[0], + heading=self.conflict.heading+90, + count=len(group.units), + at=final_position) + g.set_skill(self.game.settings.player_skill) + player_groups.append((g,group)) + + self.gen_infantry_group_for_group(g, True, self.m.country(self.game.player_country), self.conflict.heading + 90) + + # Create enemy groups at random position + for group in self.enemy_planned_combat_groups: + if group.role == CombatGroupRole.ARTILLERY: + distance_from_frontline = self.get_artilery_group_distance_from_frontline(group) + else: + distance_from_frontline = DISTANCE_FROM_FRONTLINE[group.role] + final_position = self.get_valid_position_for_group(position, False, combat_width, distance_from_frontline) + + if final_position is not None: + g = self._generate_group( + side=self.m.country(self.game.enemy_country), + unit=group.units[0], + heading=self.conflict.heading - 90, + count=len(group.units), + at=final_position) + g.set_skill(self.game.settings.enemy_vehicle_skill) + enemy_groups.append((g, group)) + + self.gen_infantry_group_for_group(g, False, self.m.country(self.game.enemy_country), self.conflict.heading - 90) + + + # Plan combat actions for groups + self.plan_action_for_groups(self.player_stance, player_groups, enemy_groups, self.conflict.heading + 90, self.conflict.from_cp, self.conflict.to_cp) + self.plan_action_for_groups(self.enemy_stance, enemy_groups, player_groups, self.conflict.heading - 90, self.conflict.to_cp, self.conflict.from_cp) + + + + def gen_infantry_group_for_group(self, group, is_player, side:Country, forward_heading): + infantry_position = group.points[0].position.random_point_within(250, 50) if side == self.conflict.attackers_country: cp = self.conflict.from_cp else: cp = self.conflict.to_cp - for c in range(count): - logging.info("armorgen: {} for {}".format(unit, side.id)) - group = self.m.vehicle_group( - side, - namegen.next_unit_name(side, cp.id, unit), - unit, - position=self._group_point(at), - group_size=1, - move_formation=move_formation) + if is_player: + faction = self.game.player_name + else: + faction = self.game.enemy_name - vehicle: Vehicle = group.units[0] + possible_infantry_units = db.find_infantry(faction) + if len(possible_infantry_units) == 0: + return + + u = random.choice(possible_infantry_units) + self.m.vehicle_group( + side, + namegen.next_infantry_name(side, cp, u), u, + position=infantry_position, + group_size=1, + heading=forward_heading, + move_formation=PointAction.OffRoad) + + for i in range(randint(3, 10)): + u = random.choice(possible_infantry_units) + position = infantry_position.random_point_within(55, 5) + self.m.vehicle_group( + side, + namegen.next_infantry_name(side, cp, u), u, + position=position, + group_size=1, + heading=forward_heading, + move_formation=PointAction.OffRoad) + + + def plan_action_for_groups(self, stance, ally_groups, enemy_groups, forward_heading, from_cp, to_cp): + + for dcs_group, group in ally_groups: + if group.role == CombatGroupRole.ARTILLERY: + # Fire on any ennemy in range + target = self.get_artillery_target_in_range(dcs_group, group, enemy_groups) + if target is not None: + dcs_group.points[0].tasks.append(FireAtPoint(target, len(group.units) * 10, 100)) + elif group.role in [CombatGroupRole.TANK, CombatGroupRole.IFV]: + if stance == CombatStance.AGGRESIVE: + # Attack nearest enemy if any + # Then move forward OR Attack enemy base if it is not too far away + target = self.find_nearest_enemy_group(dcs_group, enemy_groups) + if target is not None: + rand_offset = Point(random.randint(-50, 50), random.randint(-50, 50)) + dcs_group.add_waypoint(target.points[0].position + rand_offset, PointAction.OffRoad) + dcs_group.points[1].tasks.append(AttackGroup(target.id)) + + if to_cp.position.distance_to_point(dcs_group.points[0].position) <= AGGRESIVE_MOVE_DISTANCE: + attack_point = to_cp.position.random_point_within(500, 0) + else: + attack_point = self.find_offensive_point(dcs_group, forward_heading, AGGRESIVE_MOVE_DISTANCE) + dcs_group.add_waypoint(attack_point, PointAction.OnRoad) + elif stance == CombatStance.BREAKTHROUGH: + # In breakthrough mode, the units will move forward + # If the enemy base is close enough, the units will attack the base + if to_cp.position.distance_to_point( + dcs_group.points[0].position) <= BREAKTHROUGH_OFFENSIVE_DISTANCE: + attack_point = to_cp.position.random_point_within(500, 0) + else: + attack_point = self.find_offensive_point(dcs_group, forward_heading, BREAKTHROUGH_OFFENSIVE_DISTANCE) + dcs_group.add_waypoint(attack_point, PointAction.OnRoad) + elif stance == CombatStance.ELIMINATION: + # In elimination mode, the units focus on destroying as much enemy groups as possible + targets = self.find_n_nearest_enemy_groups(dcs_group, enemy_groups, 3) + i = 1 + for target in targets: + rand_offset = Point(random.randint(-50, 50), random.randint(-50, 50)) + dcs_group.add_waypoint(target.points[0].position+rand_offset,PointAction.OffRoad) + dcs_group.points[i].tasks.append(AttackGroup(target.id)) + i = i + 1 + if to_cp.position.distance_to_point(dcs_group.points[0].position) <= AGGRESIVE_MOVE_DISTANCE: + attack_point = to_cp.position.random_point_within(500, 0) + dcs_group.add_waypoint(attack_point) + elif group.role in [CombatGroupRole.APC, CombatGroupRole.ATGM]: + + if stance in [CombatStance.AGGRESIVE, CombatStance.BREAKTHROUGH, CombatStance.ELIMINATION]: + # APC & ATGM will never move too much forward, but will follow along any offensive + if to_cp.position.distance_to_point(dcs_group.points[0].position) <= AGGRESIVE_MOVE_DISTANCE: + attack_point = to_cp.position.random_point_within(500, 0) + else: + attack_point = self.find_offensive_point(dcs_group, forward_heading, AGGRESIVE_MOVE_DISTANCE) + dcs_group.add_waypoint(attack_point, PointAction.OnRoad) + + if stance == CombatStance.RETREAT: + # In retreat mode, the units will fall back + # If the ally base is close enough, the units will even regroup there + if from_cp.position.distance_to_point(dcs_group.points[0].position) <= RETREAT_DISTANCE: + retreat_point = from_cp.position.random_point_within(500, 250) + else: + retreat_point = self.find_retreat_point(dcs_group, forward_heading) + reposition_point = retreat_point.point_from_heading(forward_heading, 10) # Another point to make the unit face the enemy + dcs_group.add_waypoint(retreat_point, PointAction.OnRoad) + dcs_group.add_waypoint(reposition_point, PointAction.OffRoad) + + + def find_retreat_point(self, dcs_group, frontline_heading): + """ + Find a point to retreat to + :param dcs_group: DCS mission group we are searching a retreat point for + :param frontline_heading: Heading of the frontline + :return: dcs.mapping.Point object with the desired position + """ + return dcs_group.points[0].position.point_from_heading(frontline_heading-180, RETREAT_DISTANCE) + + def find_offensive_point(self, dcs_group, frontline_heading, distance): + """ + Find a point to attack + :param dcs_group: DCS mission group we are searching an attack point for + :param frontline_heading: Heading of the frontline + :param distance: Distance of the offensive (how far unit should move) + :return: dcs.mapping.Point object with the desired position + """ + return dcs_group.points[0].position.point_from_heading(frontline_heading, distance) + + def find_n_nearest_enemy_groups(self, player_group, enemy_groups, n): + """ + Return the neaarest enemy group for the player group + @param group Group for which we should find the nearest ennemies + @param enemy_groups Potential enemy groups + @param n number of nearby groups to take + """ + targets = [] + sorted_list = sorted(enemy_groups, key=lambda group: player_group.points[0].position.distance_to_point(group[0].points[0].position)) + for i in range(n): + if len(sorted_list) <= i: + break + else: + targets.append(sorted_list[i][0]) + return targets + + + def find_nearest_enemy_group(self, player_group, enemy_groups): + """ + Search the enemy groups for a potential target suitable to armored assault + @param group Group for which we should find the nearest ennemy + @param enemy_groups Potential enemy groups + """ + min_distance = 99999999 + target = None + for dcs_group, group in enemy_groups: + dist = player_group.points[0].position.distance_to_point(dcs_group.points[0].position) + if dist < min_distance: + min_distance = dist + target = dcs_group + return target + + + def get_artillery_target_in_range(self, dcs_group, group, enemy_groups): + """ + Search the enemy groups for a potential target suitable to an artillery unit + """ + rng = group.units[0].threat_range + if len(enemy_groups) == 0: + return None + for o in range(10): + potential_target = random.choice(enemy_groups)[0] + distance_to_target = dcs_group.points[0].position.distance_to_point(potential_target.points[0].position) + if distance_to_target < rng: + return potential_target.points[0].position + return None + + + def get_artilery_group_distance_from_frontline(self, group): + """ + For artilery group, decide the distance from frontline with the range of the unit + """ + rg = group.units[0].threat_range - 7500 + if rg > DISTANCE_FROM_FRONTLINE[CombatGroupRole.ARTILLERY]: + rg = DISTANCE_FROM_FRONTLINE[CombatGroupRole.ARTILLERY] + if rg < DISTANCE_FROM_FRONTLINE[CombatGroupRole.TANK]: + rg = DISTANCE_FROM_FRONTLINE[CombatGroupRole.TANK] + 100 + return rg + + + def get_valid_position_for_group(self, conflict_position, isplayer, combat_width, distance_from_frontline): + i = 0 + while i < 25: # 25 attempt for valid position + heading_diff = -90 if isplayer else 90 + shifted = conflict_position[0].point_from_heading(self.conflict.heading, + random.randint(-combat_width / 2, combat_width / 2)) + final_position = shifted.point_from_heading(self.conflict.heading + heading_diff, distance_from_frontline) + + if self.conflict.theater.is_on_land(final_position): + return final_position + else: + i = i + 1 + continue + return None + + def _generate_group(self, side: Country, unit: VehicleType, count: int, at: Point, move_formation: PointAction = PointAction.OffRoad, heading=0): + + if side == self.conflict.attackers_country: + cp = self.conflict.from_cp + else: + cp = self.conflict.to_cp + + logging.info("armorgen: {} for {}".format(unit, side.id)) + group = self.m.vehicle_group( + side, + namegen.next_unit_name(side, cp.id, unit), unit, + position=self._group_point(at), + group_size=count, + heading=heading, + move_formation=move_formation) + + for c in range(count): + vehicle: Vehicle = group.units[c] vehicle.player_can_drive = True - if not to: - to = self.conflict.position.point_from_heading(0, 500) - - wayp = group.add_waypoint(self._group_point(to), move_formation=move_formation) - wayp.tasks = [] - - def _generate_fight_at(self, attackers: db.ArmorDict, defenders: db.ArmorDict, position: Point): - - print(attackers) - print(defenders) - - if attackers: - attack_pos = position.point_from_heading(self.conflict.heading - 90, FIGHT_DISTANCE) - attack_dest = position.point_from_heading(self.conflict.heading + 90, FIGHT_DISTANCE * 2) - for type, count in attackers.items(): - self._generate_group( - side=self.conflict.attackers_country, - unit=type, - count=count, - at=attack_pos, - to=attack_dest, - ) - - if defenders: - def_pos = position.point_from_heading(self.conflict.heading + 90, FIGHT_DISTANCE) - def_dest = position.point_from_heading(self.conflict.heading - 90, FIGHT_DISTANCE * 2) - for type, count in defenders.items(): - self._generate_group( - side=self.conflict.defenders_country, - unit=type, - count=count, - at=def_pos, - to=def_dest, - ) - - def generate(self, attackers: db.ArmorDict, defenders: db.ArmorDict): - for type, count in attackers.items(): - self._generate_group( - side=self.conflict.attackers_country, - unit=type, - count=count, - at=self.conflict.ground_attackers_location) - - for type, count in defenders.items(): - self._generate_group( - side=self.conflict.defenders_country, - unit=type, - count=count, - at=self.conflict.ground_defenders_location) - - def generate_vec(self, attackers: db.ArmorDict, defenders: db.ArmorDict): - fights_count = randint(*FRONTLINE_CAS_FIGHTS_COUNT) - single_fight_defenders_count = min(int(sum(defenders.values()) / fights_count), randint(*FRONTLINE_CAS_GROUP_MIN)) - defender_groups = list(db.unitdict_split(defenders, single_fight_defenders_count)) - - single_fight_attackers_count = min(int(sum(attackers.values()) / len(defender_groups)), randint(*FRONTLINE_CAS_GROUP_MIN)) - attacker_groups = list(db.unitdict_split(attackers, single_fight_attackers_count)) - - for attacker_group_dict, target_group_dict in zip_longest(attacker_groups, defender_groups): - position = self.conflict.position.point_from_heading(self.conflict.heading, - random.randint(0, self.conflict.distance)) - self._generate_fight_at(attacker_group_dict, target_group_dict, position) - - def generate_convoy(self, units: db.ArmorDict): - for type, count in units.items(): - self._generate_group( - side=self.conflict.defenders_country, - unit=type, - count=count, - at=self.conflict.ground_defenders_location, - to=self.conflict.position, - move_formation=PointAction.OnRoad) - - def generate_passengers(self, count: int): - unit_type = random.choice(db.find_unittype(Nothing, self.conflict.attackers_side.name)) - - self.m.vehicle_group( - country=self.conflict.attackers_side, - name=namegen.next_unit_name(self.conflict.attackers_side, unit_type), - _type=unit_type, - position=self.conflict.ground_attackers_location, - group_size=count - ) + return group \ No newline at end of file diff --git a/gen/conflictgen.py b/gen/conflictgen.py index c807250c..5ced0ced 100644 --- a/gen/conflictgen.py +++ b/gen/conflictgen.py @@ -27,7 +27,7 @@ STRIKE_AIR_DEFENDERS_DISTANCE = 25000 CAP_CAS_DISTANCE = 10000, 120000 GROUND_INTERCEPT_SPREAD = 5000 -GROUND_DISTANCE_FACTOR = 1 +GROUND_DISTANCE_FACTOR = 1.4 GROUND_DISTANCE = 2000 GROUND_ATTACK_DISTANCE = 25000, 13000 diff --git a/gen/ground_forces/ai_ground_planner.py b/gen/ground_forces/ai_ground_planner.py new file mode 100644 index 00000000..18661930 --- /dev/null +++ b/gen/ground_forces/ai_ground_planner.py @@ -0,0 +1,273 @@ +import random +from enum import Enum + +from dcs.vehicles import * + +from gen.ground_forces.combat_stance import CombatStance +from theater import ControlPoint + +TYPE_TANKS = [ + Armor.MBT_T_55, + Armor.MBT_T_72B, + Armor.MBT_T_80U, + Armor.MBT_T_90, + Armor.MBT_Leopard_2, + Armor.MBT_Leopard_1A3, + Armor.MBT_Leclerc, + Armor.MBT_Challenger_II, + Armor.MBT_M1A2_Abrams, + Armor.MBT_M60A3_Patton, + Armor.MBT_Merkava_Mk__4, + Armor.MT_Pz_Kpfw_V_Panther_Ausf_G, + Armor.MT_Pz_Kpfw_IV_Ausf_H, + Armor.HT_Pz_Kpfw_VI_Tiger_I, + Armor.HT_Pz_Kpfw_VI_Ausf__B__Tiger_II, + Armor.MT_M4_Sherman, + Armor.MT_M4A4_Sherman_Firefly, + Armor.StuG_IV, + Armor.ZTZ_96B +] + +TYPE_ATGM = [ + Armor.ATGM_M1045_HMMWV_TOW, + Armor.ATGM_M1134_Stryker, + Armor.IFV_BMP_2, +] + +TYPE_IFV = [ + Armor.IFV_BMP_3, + Armor.IFV_BMP_2, + Armor.IFV_BMP_1, + Armor.IFV_Marder, + Armor.IFV_MCV_80, + Armor.IFV_LAV_25, + Armor.IFV_Sd_Kfz_234_2_Puma, + Armor.IFV_M2A2_Bradley, + Armor.IFV_BMD_1, + Armor.ZBD_04A, + Armor.APC_Sd_Kfz_251, + Armor.IFV_Sd_Kfz_234_2_Puma, +] + +TYPE_APC = [ + Armor.APC_M1043_HMMWV_Armament, + Armor.APC_M1126_Stryker_ICV, + Armor.APC_M113, + Armor.APC_BTR_80, + Armor.APC_MTLB, + Armor.APC_M2A1, + Armor.APC_Cobra, + Armor.APC_Sd_Kfz_251, + Armor.APC_AAV_7, + Armor.TPz_Fuchs, + Armor.ARV_BRDM_2, + Armor.ARV_BTR_RD, + Armor.ARV_MTLB_U_BOMAN, + Armor.M30_Cargo_Carrier, + Armor.APC_M2A1, +] + +TYPE_ARTILLERY = [ + Artillery.MLRS_9A52_Smerch, + Artillery.SPH_2S1_Gvozdika, + Artillery.SPH_2S3_Akatsia, + Artillery.MLRS_BM_21_Grad, + Artillery.MLRS_9K57_Uragan_BM_27, + Artillery.SPH_M109_Paladin, + Artillery.MLRS_M270, + Artillery.SPH_2S9_Nona, + Artillery.SpGH_Dana, + Artillery.SPH_2S19_Msta, + Artillery.M12_GMC, + Artillery.MLRS_FDDM, + Artillery.Sturmpanzer_IV_Brummbär +] + +TYPE_LOGI = [ + Unarmed.Transport_M818, + Unarmed.Transport_KAMAZ_43101, + Unarmed.Transport_Ural_375, + Unarmed.Transport_GAZ_66, + Unarmed.Transport_GAZ_3307, + Unarmed.Transport_GAZ_3308, + Unarmed.Transport_Ural_4320_31_Armored, + Unarmed.Transport_Ural_4320T, + Unarmed.Blitz_3_6_6700A, + Unarmed.Kübelwagen_82, + Unarmed.Sd_Kfz_7, + Unarmed.Sd_Kfz_2, + Unarmed.Willys_MB, + Unarmed.Land_Rover_109_S3, + Unarmed.Land_Rover_101_FC, +] + +TYPE_INFANTRY = [ + Infantry.Infantry_Soldier_Insurgents, + Infantry.Soldier_AK, + Infantry.Infantry_M1_Garand, + Infantry.Infantry_Mauser_98, + Infantry.Infantry_SMLE_No_4_Mk_1, + Infantry.Georgian_soldier_with_M4, + Infantry.Infantry_Soldier_Rus, + Infantry.Paratrooper_AKS, + Infantry.Paratrooper_RPG_16, + Infantry.Soldier_M249, + Infantry.Infantry_M4, + Infantry.Soldier_RPG, +] + +MAX_COMBAT_GROUP_PER_CP = 10 + +class CombatGroupRole(Enum): + TANK = 1 + APC = 2 + IFV = 3 + ARTILLERY = 4 + SHORAD = 5 + LOGI = 6 + INFANTRY = 7 + ATGM = 8 + + +DISTANCE_FROM_FRONTLINE = { + CombatGroupRole.TANK:2800, + CombatGroupRole.APC:7000, + CombatGroupRole.IFV:3000, + CombatGroupRole.ARTILLERY:14000, + CombatGroupRole.SHORAD:12000, + CombatGroupRole.LOGI:18000, + CombatGroupRole.INFANTRY:2800, + CombatGroupRole.ATGM:5500 +} + +GROUP_SIZES_BY_COMBAT_STANCE = { + CombatStance.DEFENSIVE: [2, 4, 6], + CombatStance.AGGRESIVE: [2, 4, 6], + CombatStance.RETREAT: [2, 4, 6, 8], + CombatStance.BREAKTHROUGH: [4, 6, 6, 8], + CombatStance.ELIMINATION: [2, 4, 4, 4, 6], + CombatStance.AMBUSH: [1, 1, 2, 2, 2, 2, 4] +} + + +class CombatGroup: + + def __init__(self, role:CombatGroupRole): + self.units = [] + self.role = role + self.assigned_enemy_cp = None + + def __str__(self): + s = "" + s += "ROLE : " + str(self.role) + "\n" + if len(self.units) > 0: + s += "UNITS " + self.units[0].name + " * " + str(len(self.units)) + return s + +class GroundPlanner: + + cp = None + combat_groups_dict = {} + connected_enemy_cp = [] + + tank_groups = [] + apc_group = [] + ifv_group = [] + art_group = [] + shorad_groups = [] + logi_groups = [] + + def __init__(self, cp:ControlPoint, game): + self.cp = cp + self.game = game + self.connected_enemy_cp = [cp for cp in self.cp.connected_points if cp.captured != self.cp.captured] + self.tank_groups = [] + self.apc_group = [] + self.ifv_group = [] + self.art_group = [] + self.atgm_group = [] + self.logi_groups = [] + self.shorad_groups = [] + + self.units_per_cp = {} + for cp in self.connected_enemy_cp: + self.units_per_cp[cp.id] = [] + self.reserve = [] + + + def plan_groundwar(self): + + if hasattr(self.cp, 'stance'): + group_size_choice = GROUP_SIZES_BY_COMBAT_STANCE[self.cp.stance] + else: + self.cp.stance = CombatStance.DEFENSIVE + group_size_choice = GROUP_SIZES_BY_COMBAT_STANCE[CombatStance.DEFENSIVE] + + # Create combat groups and assign them randomly to each enemy CP + for key in self.cp.base.armor.keys(): + + role = None + collection = None + if key in TYPE_TANKS: + collection = self.tank_groups + role = CombatGroupRole.TANK + elif key in TYPE_APC: + collection = self.apc_group + role = CombatGroupRole.APC + elif key in TYPE_ARTILLERY: + collection = self.art_group + role = CombatGroupRole.ARTILLERY + elif key in TYPE_IFV: + collection = self.ifv_group + role = CombatGroupRole.IFV + elif key in TYPE_LOGI: + collection = self.logi_groups + role = CombatGroupRole.LOGI + elif key in TYPE_ATGM: + collection = self.atgm_group + role = CombatGroupRole.ATGM + else: + print("Warning unit type not handled by ground generator") + print(key) + continue + + available = self.cp.base.armor[key] + while available > 0: + n = random.choice(group_size_choice) + if n > available: + if available >= 2: + n = 2 + else: + n = 1 + available -= n + + group = CombatGroup(role) + if len(self.connected_enemy_cp) > 0: + enemy_cp = random.choice(self.connected_enemy_cp).id + self.units_per_cp[enemy_cp].append(group) + group.assigned_enemy_cp = enemy_cp + else: + self.reserve.append(group) + group.assigned_enemy_cp = "__reserve__" + + for i in range(n): + group.units.append(key) + collection.append(group) + + print("------------------") + print("Ground Planner : ") + print(self.cp.name) + print("------------------") + for key in self.units_per_cp.keys(): + print("For : #" + str(key)) + for group in self.units_per_cp[key]: + print(str(group)) + + + + + + + + + diff --git a/gen/ground_forces/combat_stance.py b/gen/ground_forces/combat_stance.py new file mode 100644 index 00000000..d2247f73 --- /dev/null +++ b/gen/ground_forces/combat_stance.py @@ -0,0 +1,11 @@ +from enum import Enum + + +class CombatStance(Enum): + DEFENSIVE = 0 # Unit will adopt defensive stance with medium group of units + AGGRESIVE = 1 # Unit will attempt to make progress with medium sized group of units + RETREAT = 2 # Unit will retreat + BREAKTHROUGH = 3 # Unit will attempt a breakthrough, rushing forward very aggresively with big group of armored units, and even less armored units will move aggresively + ELIMINATION = 4 # Unit will progress aggresively toward anemy units, attempting to eliminate the ennemy force + AMBUSH = 5 # Units will adopt a defensive stance a bit different from 'DEFENSIVE', ATGM & INFANTRY with RPG will be located on frontline with the armored units. (The groups of units will be smaller) + diff --git a/gen/groundobjectsgen.py b/gen/groundobjectsgen.py index 0df9cebf..6ad68a3e 100644 --- a/gen/groundobjectsgen.py +++ b/gen/groundobjectsgen.py @@ -96,6 +96,8 @@ class GroundObjectsGenerator: ship.heading = u.heading sg.add_unit(ship) + sg.add_waypoint(sg.points[0].position.point_from_heading(g.units[0].heading, 100000)) + else: if ground_object.dcs_identifier in warehouse_map: static_type = warehouse_map[ground_object.dcs_identifier] diff --git a/gen/naming.py b/gen/naming.py index 0a0cffcb..59a04118 100644 --- a/gen/naming.py +++ b/gen/naming.py @@ -8,6 +8,10 @@ class NameGenerator: self.number += 1 return "unit|{}|{}|{}|{}|".format(country.id, self.number, parent_base_id, db.unit_type_name(unit_type)) + def next_infantry_name(self, country, parent_base_id, unit_type): + self.number += 1 + return "infantry|{}|{}|{}|{}|".format(country.id, self.number, parent_base_id, db.unit_type_name(unit_type)) + def next_basedefense_name(self): return "basedefense_aa|0|0|" diff --git a/gen/visualgen.py b/gen/visualgen.py index 5300954d..c3be7cad 100644 --- a/gen/visualgen.py +++ b/gen/visualgen.py @@ -126,6 +126,8 @@ class VisualGenerator: break def _generate_stub_planes(self): + pass + """ mission_units = set() for coalition_name, coalition in self.mission.coalition.items(): for country in coalition.countries.values(): @@ -134,7 +136,7 @@ class VisualGenerator: mission_units.add(db.unit_type_of(unit)) for unit_type in mission_units: - self.mission.static_group(self.mission.country(self.game.player_country), "a", unit_type, Point(0, 300000), hidden=True) + self.mission.static_group(self.mission.country(self.game.player_country), "a", unit_type, Point(0, 300000), hidden=True)""" def generate_target_smokes(self, target): spread = target.size * DESTINATION_SMOKE_DISTANCE_FACTOR diff --git a/qt_ui/uiconstants.py b/qt_ui/uiconstants.py index 1cef14aa..7c493eb0 100644 --- a/qt_ui/uiconstants.py +++ b/qt_ui/uiconstants.py @@ -4,8 +4,7 @@ from typing import Dict from PySide2.QtGui import QColor, QFont, QPixmap -from game.event import BaseAttackEvent, FrontlinePatrolEvent, FrontlineAttackEvent, InfantryTransportEvent, \ - InsurgentAttackEvent, ConvoyStrikeEvent, InterceptEvent, NavalInterceptEvent, StrikeEvent, UnitsDeliveryEvent +from game.event import UnitsDeliveryEvent, FrontlineAttackEvent from theater.theatergroundobject import CATEGORY_MAP URLS : Dict[str, str] = { @@ -85,15 +84,9 @@ EVENT_ICONS: Dict[str, QPixmap] = {} def load_event_icons(): - for category, image in {BaseAttackEvent: "capture", - FrontlinePatrolEvent: "attack", + for category, image in { + "strike": "strike", FrontlineAttackEvent: "attack", - InfantryTransportEvent: "infantry", - InsurgentAttackEvent: "insurgent_attack", - ConvoyStrikeEvent: "convoy", - InterceptEvent: "air_intercept", - NavalInterceptEvent: "naval_intercept", - StrikeEvent: "strike", UnitsDeliveryEvent: "delivery"}.items(): EVENT_ICONS[category] = QPixmap("./resources/ui/events/" + image + ".png") diff --git a/qt_ui/widgets/base/QBaseInformation.py b/qt_ui/widgets/base/QBaseInformation.py index 1fd7968b..dc1e2660 100644 --- a/qt_ui/widgets/base/QBaseInformation.py +++ b/qt_ui/widgets/base/QBaseInformation.py @@ -11,6 +11,7 @@ class QBaseInformation(QGroupBox): super(QBaseInformation, self).__init__("Base defenses") self.cp = cp self.airport = airport + self.setMinimumWidth(500) self.init_ui() def init_ui(self): diff --git a/qt_ui/widgets/map/QLiberationMap.py b/qt_ui/widgets/map/QLiberationMap.py index 3ce31fa0..6c713b71 100644 --- a/qt_ui/widgets/map/QLiberationMap.py +++ b/qt_ui/widgets/map/QLiberationMap.py @@ -8,8 +8,7 @@ from dcs import Point import qt_ui.uiconstants as CONST from game import Game, db -from game.event import InfantryTransportEvent, StrikeEvent, BaseAttackEvent, UnitsDeliveryEvent, Event, \ - FrontlineAttackEvent, FrontlinePatrolEvent, ConvoyStrikeEvent, ControlPointType +from game.event import UnitsDeliveryEvent, Event, ControlPointType from gen import Conflict from qt_ui.widgets.map.QLiberationScene import QLiberationScene from qt_ui.widgets.map.QMapControlPoint import QMapControlPoint diff --git a/qt_ui/widgets/map/QMapControlPoint.py b/qt_ui/widgets/map/QMapControlPoint.py index dfc3d0d4..d0da6371 100644 --- a/qt_ui/widgets/map/QMapControlPoint.py +++ b/qt_ui/widgets/map/QMapControlPoint.py @@ -5,7 +5,8 @@ from PySide2.QtWidgets import QGraphicsRectItem, QGraphicsSceneHoverEvent, QGrap import qt_ui.uiconstants as CONST from game import Game -from qt_ui.windows.QBaseMenu import QBaseMenu +from qt_ui.windows.basemenu.QBaseMenu import QBaseMenu +from qt_ui.windows.basemenu.QBaseMenu2 import QBaseMenu2 from theater import ControlPoint, db @@ -98,5 +99,5 @@ class QMapControlPoint(QGraphicsRectItem): return self.model.captured and CONST.COLORS["bright_red"] or CONST.COLORS["dark_blue"] def openBaseMenu(self): - self.baseMenu = QBaseMenu(self.window(), self.model, self.game) + self.baseMenu = QBaseMenu2(self.window(), self.model, self.game) self.baseMenu.show() \ No newline at end of file diff --git a/qt_ui/windows/QBriefingWindow.py b/qt_ui/windows/QBriefingWindow.py index 01f967e4..7b4eec33 100644 --- a/qt_ui/windows/QBriefingWindow.py +++ b/qt_ui/windows/QBriefingWindow.py @@ -5,7 +5,7 @@ from PySide2.QtWidgets import QHBoxLayout, QLabel, QWidget, QDialog, QVBoxLayout QSpinBox, QPushButton, QMessageBox, QComboBox from pip._internal.utils import typing -from game.game import AWACS_BUDGET_COST, PinpointStrike, db, Event, FrontlineAttackEvent, FrontlinePatrolEvent, Task, \ +from game.game import AWACS_BUDGET_COST, PinpointStrike, db, Event, FrontlineAttackEvent, Task, \ UnitType from qt_ui.windows.QWaitingForMissionResultWindow import QWaitingForMissionResultWindow from userdata.persistency import base_path @@ -234,14 +234,14 @@ class QBriefingWindow(QDialog): return if self.game.is_player_attack(self.gameEvent): - if isinstance(self.gameEvent, FrontlineAttackEvent) or isinstance(self.gameEvent, FrontlinePatrolEvent): + if isinstance(self.gameEvent, FrontlineAttackEvent): if self.base.total_armor == 0: self.showErrorMessage("No ground vehicles available to attack!") return self.gameEvent.player_attacking(flights) else: - if isinstance(self.gameEvent, FrontlineAttackEvent) or isinstance(self.gameEvent, FrontlinePatrolEvent): + if isinstance(self.gameEvent, FrontlineAttackEvent): if self.gameEvent.to_cp.base.total_armor == 0: self.showErrorMessage("No ground vehicles available to defend!") return diff --git a/qt_ui/windows/QBaseMenu.py b/qt_ui/windows/basemenu/QBaseMenu.py similarity index 100% rename from qt_ui/windows/QBaseMenu.py rename to qt_ui/windows/basemenu/QBaseMenu.py diff --git a/qt_ui/windows/basemenu/QBaseMenu2.py b/qt_ui/windows/basemenu/QBaseMenu2.py new file mode 100644 index 00000000..385a0cbc --- /dev/null +++ b/qt_ui/windows/basemenu/QBaseMenu2.py @@ -0,0 +1,73 @@ +import traceback + +from PySide2.QtCore import Qt +from PySide2.QtGui import QCloseEvent +from PySide2.QtWidgets import QHBoxLayout, QLabel, QWidget, QDialog, QVBoxLayout, QGridLayout, QPushButton, \ + QGroupBox, QSizePolicy, QSpacerItem +from dcs.unittype import UnitType + +from game.event import UnitsDeliveryEvent, ControlPointType +from qt_ui.widgets.QBudgetBox import QBudgetBox +from qt_ui.widgets.base.QAirportInformation import QAirportInformation +from qt_ui.widgets.base.QBaseInformation import QBaseInformation +from qt_ui.windows.basemenu.QBaseMenuTabs import QBaseMenuTabs +from qt_ui.windows.mission.QPlannedFlightsView import QPlannedFlightsView +from qt_ui.windows.GameUpdateSignal import GameUpdateSignal +from theater import ControlPoint, CAP, Embarking, CAS, PinpointStrike, db +from game import Game + + +class QBaseMenu2(QDialog): + + def __init__(self, parent, cp: ControlPoint, game: Game): + super(QBaseMenu2, self).__init__(parent) + + # Attrs + self.cp = cp + self.game = game + self.is_carrier = self.cp.cptype in [ControlPointType.AIRCRAFT_CARRIER_GROUP, ControlPointType.LHA_GROUP] + + # Widgets + self.qbase_menu_tab = QBaseMenuTabs(cp, game) + + try: + self.airport = game.theater.terrain.airport_by_id(self.cp.id) + except: + self.airport = None + + if self.cp.captured: + self.deliveryEvent = None + + self.setWindowFlags(Qt.WindowStaysOnTopHint) + self.setMinimumSize(300, 200) + self.setModal(True) + self.initUi() + + def initUi(self): + self.setWindowTitle(self.cp.name) + self.topLayoutWidget = QWidget() + self.topLayout = QHBoxLayout() + + self.topLayoutWidget = QWidget() + self.topLayout = QHBoxLayout() + + title = QLabel("" + self.cp.name + "") + title.setAlignment(Qt.AlignLeft | Qt.AlignTop) + title.setProperty("style", "base-title") + unitsPower = QLabel("{} / {} / Runway : {}".format(self.cp.base.total_planes, self.cp.base.total_armor, + "Available" if self.cp.has_runway() else "Unavailable")) + + self.topLayout.addWidget(title) + self.topLayout.addWidget(unitsPower) + self.topLayout.setAlignment(Qt.AlignTop) + self.topLayoutWidget.setProperty("style", "baseMenuHeader") + self.topLayoutWidget.setLayout(self.topLayout) + + self.mainLayout = QGridLayout() + self.mainLayout.addWidget(self.topLayoutWidget, 0, 0) + self.mainLayout.addWidget(self.qbase_menu_tab, 1, 0) + + self.setLayout(self.mainLayout) + + def closeEvent(self, closeEvent:QCloseEvent): + GameUpdateSignal.get_instance().updateGame(self.game) \ No newline at end of file diff --git a/qt_ui/windows/basemenu/QBaseMenuTabs.py b/qt_ui/windows/basemenu/QBaseMenuTabs.py new file mode 100644 index 00000000..775a4e35 --- /dev/null +++ b/qt_ui/windows/basemenu/QBaseMenuTabs.py @@ -0,0 +1,37 @@ +from PySide2.QtWidgets import QTabWidget, QFrame, QGridLayout, QLabel + +from game import Game +from qt_ui.windows.basemenu.airfield.QAirfieldCommand import QAirfieldCommand +from qt_ui.windows.basemenu.base_defenses.QBaseDefensesHQ import QBaseDefensesHQ +from qt_ui.windows.basemenu.ground_forces.QGroundForcesHQ import QGroundForcesHQ +from qt_ui.windows.basemenu.intel.QIntelInfo import QIntelInfo +from theater import ControlPoint + + +class QBaseMenuTabs(QTabWidget): + + def __init__(self, cp: ControlPoint, game: Game): + super(QBaseMenuTabs, self).__init__() + self.cp = cp + if cp: + + if not cp.captured: + self.intel = QIntelInfo(cp, game) + self.addTab(self.intel, "Intel") + else: + if cp.has_runway(): + self.airfield_command = QAirfieldCommand(cp, game) + self.addTab(self.airfield_command, "Airfield Command") + + if not cp.is_carrier: + self.ground_forces_hq = QGroundForcesHQ(cp, game) + self.addTab(self.ground_forces_hq, "Ground Forces HQ") + self.base_defenses_hq = QBaseDefensesHQ(cp, game) + self.addTab(self.base_defenses_hq, "Base Defenses") + + else: + tabError = QFrame() + l = QGridLayout() + l.addWidget(QLabel("No Control Point")) + tabError.setLayout(l) + self.addTab(tabError, "No Control Point") \ No newline at end of file diff --git a/qt_ui/windows/basemenu/QRecruitBehaviour.py b/qt_ui/windows/basemenu/QRecruitBehaviour.py new file mode 100644 index 00000000..096caeff --- /dev/null +++ b/qt_ui/windows/basemenu/QRecruitBehaviour.py @@ -0,0 +1,92 @@ +from PySide2.QtWidgets import QLabel, QPushButton, \ + QSizePolicy, QSpacerItem +from dcs.unittype import UnitType + +from theater import db + + +class QRecruitBehaviour: + + game = None + cp = None + deliveryEvent = None + existing_units_labels = None + bought_amount_labels = None + + def __init__(self): + self.bought_amount_labels = {} + self.existing_units_labels = {} + + def add_purchase_row(self, unit_type, layout, row): + + existing_units = self.cp.base.total_units_of_type(unit_type) + scheduled_units = self.deliveryEvent.units.get(unit_type, 0) + + unitName = QLabel("" + db.unit_type_name(unit_type) + "") + unitName.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)) + + existing_units = QLabel(str(existing_units)) + existing_units.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) + + amount_bought = QLabel("[{}]".format(str(scheduled_units))) + amount_bought.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) + + self.existing_units_labels[unit_type] = existing_units + self.bought_amount_labels[unit_type] = amount_bought + + price = QLabel("{}m".format(db.PRICES[unit_type])) + price.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) + + buy = QPushButton("+") + buy.setProperty("style", "btn-success") + buy.setMinimumSize(24, 24) + buy.clicked.connect(lambda: self.buy(unit_type)) + buy.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) + + sell = QPushButton("-") + sell.setProperty("style", "btn-danger") + sell.setMinimumSize(24, 24) + sell.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) + sell.clicked.connect(lambda: self.sell(unit_type)) + + layout.addWidget(unitName, row, 0) + layout.addItem(QSpacerItem(20, 0, QSizePolicy.Minimum, QSizePolicy.Minimum), row, 1) + layout.addWidget(existing_units, row, 2) + layout.addWidget(amount_bought, row, 3) + layout.addWidget(price, row, 4) + layout.addItem(QSpacerItem(20, 0, QSizePolicy.Minimum, QSizePolicy.Minimum), row, 5) + layout.addWidget(buy, row, 6) + layout.addWidget(sell, row, 7) + + return row + 1 + + def _update_count_label(self, unit_type: UnitType): + + self.bought_amount_labels[unit_type].setText("[{}]".format( + unit_type in self.deliveryEvent.units and "{}".format(self.deliveryEvent.units[unit_type]) or "0" + )) + + self.existing_units_labels[unit_type].setText("{}".format( + self.cp.base.total_units_of_type(unit_type) + )) + + def buy(self, unit_type): + price = db.PRICES[unit_type] + if self.game.budget >= price: + self.deliveryEvent.deliver({unit_type: 1}) + self.game.budget -= price + self._update_count_label(unit_type) + + def sell(self, unit_type): + if self.deliveryEvent.units.get(unit_type, 0) > 0: + price = db.PRICES[unit_type] + self.game.budget += price + self.deliveryEvent.units[unit_type] = self.deliveryEvent.units[unit_type] - 1 + if self.deliveryEvent.units[unit_type] == 0: + del self.deliveryEvent.units[unit_type] + elif self.cp.base.total_units_of_type(unit_type) > 0: + price = db.PRICES[unit_type] + self.game.budget += price + self.cp.base.commit_losses({unit_type: 1}) + + self._update_count_label(unit_type) diff --git a/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py new file mode 100644 index 00000000..68999940 --- /dev/null +++ b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py @@ -0,0 +1,63 @@ +import traceback + +from PySide2.QtCore import Qt +from PySide2.QtGui import QCloseEvent +from PySide2.QtWidgets import QHBoxLayout, QLabel, QWidget, QDialog, QVBoxLayout, QGridLayout, QPushButton, \ + QGroupBox, QSizePolicy, QSpacerItem, QFrame +from dcs.unittype import UnitType + +from game.event import UnitsDeliveryEvent, ControlPointType +from qt_ui.widgets.QBudgetBox import QBudgetBox +from qt_ui.widgets.base.QAirportInformation import QAirportInformation +from qt_ui.widgets.base.QBaseInformation import QBaseInformation +from qt_ui.windows.basemenu.QRecruitBehaviour import QRecruitBehaviour +from qt_ui.windows.mission.QPlannedFlightsView import QPlannedFlightsView +from qt_ui.windows.GameUpdateSignal import GameUpdateSignal +from theater import ControlPoint, CAP, Embarking, CAS, PinpointStrike, db +from game import Game + + +class QAircraftRecruitmentMenu(QGroupBox, QRecruitBehaviour): + + def __init__(self, cp:ControlPoint, game:Game): + QGroupBox.__init__(self, "Recruitment") + self.cp = cp + self.game = game + + self.bought_amount_labels = {} + self.existing_units_labels = {} + + for event in self.game.events: + if event.__class__ == UnitsDeliveryEvent and event.from_cp == self.cp: + self.deliveryEvent = event + if not self.deliveryEvent: + self.deliveryEvent = self.game.units_delivery_event(self.cp) + + self.init_ui() + + def init_ui(self): + layout = QVBoxLayout() + + units = { + CAP: db.find_unittype(CAP, self.game.player_name), + CAS: db.find_unittype(CAS, self.game.player_name), + } + + task_box_layout = QGridLayout() + row = 0 + + for task_type in units.keys(): + units_column = list(set(units[task_type])) + if len(units_column) == 0: continue + units_column.sort(key=lambda x: db.PRICES[x]) + for unit_type in units_column: + if self.cp.is_carrier and not unit_type in db.CARRIER_CAPABLE: + continue + row = self.add_purchase_row(unit_type, task_box_layout, row) + stretch = QVBoxLayout() + stretch.addStretch() + task_box_layout.addLayout(stretch, row, 0) + + layout.addLayout(task_box_layout) + layout.addStretch() + self.setLayout(layout) diff --git a/qt_ui/windows/basemenu/airfield/QAirfieldCommand.py b/qt_ui/windows/basemenu/airfield/QAirfieldCommand.py new file mode 100644 index 00000000..74b3c973 --- /dev/null +++ b/qt_ui/windows/basemenu/airfield/QAirfieldCommand.py @@ -0,0 +1,31 @@ +from PySide2.QtWidgets import QFrame, QGridLayout, QLabel, QHBoxLayout, QGroupBox, QVBoxLayout +from game import Game +from qt_ui.widgets.base.QAirportInformation import QAirportInformation +from qt_ui.windows.basemenu.airfield.QAircraftRecruitmentMenu import QAircraftRecruitmentMenu +from qt_ui.windows.mission.QPlannedFlightsView import QPlannedFlightsView +from theater import ControlPoint + + +class QAirfieldCommand(QFrame): + + def __init__(self, cp:ControlPoint, game:Game): + super(QAirfieldCommand, self).__init__() + self.cp = cp + self.game = game + self.init_ui() + + def init_ui(self): + layout = QGridLayout() + layout.addWidget(QAircraftRecruitmentMenu(self.cp, self.game), 0, 0) + + try: + planned = QGroupBox("Planned Flights") + planned_layout = QVBoxLayout() + planned_layout.addWidget(QPlannedFlightsView(self.game.planners[self.cp.id])) + planned.setLayout(planned_layout) + layout.addWidget(planned, 0, 1) + except: + pass + + #layout.addWidget(QAirportInformation(self.cp, self.game.theater.terrain.airport_by_id(self.cp.id)), 0, 2) + self.setLayout(layout) diff --git a/qt_ui/windows/basemenu/base_defenses/QBaseDefensesHQ.py b/qt_ui/windows/basemenu/base_defenses/QBaseDefensesHQ.py new file mode 100644 index 00000000..7dfec6d4 --- /dev/null +++ b/qt_ui/windows/basemenu/base_defenses/QBaseDefensesHQ.py @@ -0,0 +1,20 @@ +from PySide2.QtWidgets import QFrame, QGridLayout, QLabel +from game import Game +from qt_ui.widgets.base.QBaseInformation import QBaseInformation +from theater import ControlPoint + + +class QBaseDefensesHQ(QFrame): + + def __init__(self, cp:ControlPoint, game:Game): + super(QBaseDefensesHQ, self).__init__() + self.cp = cp + self.game = game + self.init_ui() + + def init_ui(self): + airport = self.game.theater.terrain.airport_by_id(self.cp.id) + layout = QGridLayout() + layout.addWidget(QBaseInformation(self.cp, airport)) + self.setLayout(layout) + diff --git a/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py b/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py new file mode 100644 index 00000000..24e834d4 --- /dev/null +++ b/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py @@ -0,0 +1,49 @@ +from PySide2.QtWidgets import QVBoxLayout, QGridLayout, QGroupBox + +from game import Game +from game.event import UnitsDeliveryEvent +from qt_ui.windows.basemenu.QRecruitBehaviour import QRecruitBehaviour +from theater import ControlPoint, PinpointStrike, db + + +class QArmorRecruitmentMenu(QGroupBox, QRecruitBehaviour): + + def __init__(self, cp:ControlPoint, game:Game): + QGroupBox.__init__(self, "Recruitment") + self.cp = cp + self.game = game + + self.bought_amount_labels = {} + self.existing_units_labels = {} + + for event in self.game.events: + if event.__class__ == UnitsDeliveryEvent and event.from_cp == self.cp: + self.deliveryEvent = event + if not self.deliveryEvent: + self.deliveryEvent = self.game.units_delivery_event(self.cp) + + self.init_ui() + + def init_ui(self): + layout = QVBoxLayout() + + units = { + PinpointStrike: db.find_unittype(PinpointStrike, self.game.player_name), + } + + task_box_layout = QGridLayout() + row = 0 + + for task_type in units.keys(): + units_column = list(set(units[task_type])) + if len(units_column) == 0: continue + units_column.sort(key=lambda x: db.PRICES[x]) + for unit_type in units_column: + row = self.add_purchase_row(unit_type, task_box_layout, row) + stretch = QVBoxLayout() + stretch.addStretch() + task_box_layout.addLayout(stretch, row, 0) + + layout.addLayout(task_box_layout) + layout.addStretch() + self.setLayout(layout) diff --git a/qt_ui/windows/basemenu/ground_forces/QGroundForcesHQ.py b/qt_ui/windows/basemenu/ground_forces/QGroundForcesHQ.py new file mode 100644 index 00000000..1ea116e3 --- /dev/null +++ b/qt_ui/windows/basemenu/ground_forces/QGroundForcesHQ.py @@ -0,0 +1,21 @@ +from PySide2.QtWidgets import QFrame, QGridLayout + +from game import Game +from qt_ui.windows.basemenu.ground_forces.QArmorRecruitmentMenu import QArmorRecruitmentMenu +from qt_ui.windows.basemenu.ground_forces.QGroundForcesStrategy import QGroundForcesStrategy +from theater import ControlPoint + + +class QGroundForcesHQ(QFrame): + + def __init__(self, cp:ControlPoint, game:Game): + super(QGroundForcesHQ, self).__init__() + self.cp = cp + self.game = game + self.init_ui() + + def init_ui(self): + layout = QGridLayout() + layout.addWidget(QArmorRecruitmentMenu(self.cp, self.game), 0, 0) + layout.addWidget(QGroundForcesStrategy(self.cp, self.game), 0, 1) + self.setLayout(layout) diff --git a/qt_ui/windows/basemenu/ground_forces/QGroundForcesStrategy.py b/qt_ui/windows/basemenu/ground_forces/QGroundForcesStrategy.py new file mode 100644 index 00000000..0b7b4db6 --- /dev/null +++ b/qt_ui/windows/basemenu/ground_forces/QGroundForcesStrategy.py @@ -0,0 +1,23 @@ +from PySide2.QtWidgets import QLabel, QGroupBox, QVBoxLayout + +from game import Game +from qt_ui.windows.basemenu.ground_forces.QGroundForcesStrategySelector import QGroundForcesStrategySelector +from theater import ControlPoint + + +class QGroundForcesStrategy(QGroupBox): + + def __init__(self, cp:ControlPoint, game:Game): + super(QGroundForcesStrategy, self).__init__("Frontline operations :") + self.cp = cp + self.game = game + self.init_ui() + + def init_ui(self): + layout = QVBoxLayout() + for enemy_cp in self.cp.connected_points: + if not enemy_cp.captured: + layout.addWidget(QLabel(enemy_cp.name)) + layout.addWidget(QGroundForcesStrategySelector(self.cp, enemy_cp)) + layout.addStretch() + self.setLayout(layout) diff --git a/qt_ui/windows/basemenu/ground_forces/QGroundForcesStrategySelector.py b/qt_ui/windows/basemenu/ground_forces/QGroundForcesStrategySelector.py new file mode 100644 index 00000000..2ccdf40b --- /dev/null +++ b/qt_ui/windows/basemenu/ground_forces/QGroundForcesStrategySelector.py @@ -0,0 +1,25 @@ +from PySide2.QtWidgets import QComboBox + +from theater import ControlPoint, CombatStance + + +class QGroundForcesStrategySelector(QComboBox): + + def __init__(self, cp:ControlPoint, enemy_cp:ControlPoint): + super(QGroundForcesStrategySelector, self).__init__() + self.cp = cp + self.enemy_cp = enemy_cp + + if enemy_cp.id not in self.cp.stances: + self.cp.stances[enemy_cp.id] = CombatStance.DEFENSIVE + + for i, stance in enumerate(CombatStance): + self.addItem(stance.name, userData=stance.value) + if self.cp.stances[enemy_cp.id] == stance.value: + self.setCurrentIndex(i) + + self.currentTextChanged.connect(self.on_change) + + def on_change(self): + print(self.currentData()) + self.cp.stances[self.enemy_cp.id] = self.currentData() diff --git a/qt_ui/windows/basemenu/intel/QIntelInfo.py b/qt_ui/windows/basemenu/intel/QIntelInfo.py new file mode 100644 index 00000000..bc7cb13b --- /dev/null +++ b/qt_ui/windows/basemenu/intel/QIntelInfo.py @@ -0,0 +1,56 @@ + + +from PySide2.QtWidgets import QLabel, QGroupBox, QVBoxLayout, QFrame, QGridLayout +from dcs.task import Embarking, CAS, PinpointStrike, CAP + +from game import Game +from qt_ui.windows.basemenu.ground_forces.QGroundForcesStrategySelector import QGroundForcesStrategySelector +from theater import ControlPoint, db + + +class QIntelInfo(QFrame): + + def __init__(self, cp:ControlPoint, game:Game): + super(QIntelInfo, self).__init__() + self.cp = cp + self.game = game + self.init_ui() + + def init_ui(self): + layout = QVBoxLayout() + + intel = QGroupBox("Intel") + intelLayout = QVBoxLayout() + + units = { + CAP: db.find_unittype(CAP, self.game.enemy_name), + Embarking: db.find_unittype(Embarking, self.game.enemy_name), + CAS: db.find_unittype(CAS, self.game.enemy_name), + PinpointStrike: db.find_unittype(PinpointStrike, self.game.enemy_name), + } + + for task_type in units.keys(): + units_column = list(set(units[task_type])) + + if sum([self.cp.base.total_units_of_type(u) for u in units_column]) > 0: + + group = QGroupBox(db.task_name(task_type)) + groupLayout = QGridLayout() + group.setLayout(groupLayout) + + row = 0 + for unit_type in units_column: + existing_units = self.cp.base.total_units_of_type(unit_type) + if existing_units == 0: + continue + groupLayout.addWidget(QLabel("" + db.unit_type_name(unit_type) + ""), row, 0) + groupLayout.addWidget(QLabel(str(existing_units)), row, 1) + row += 1 + + intelLayout.addWidget(group) + + intelLayout.addStretch() + intel.setLayout(intelLayout) + layout.addWidget(intel) + layout.addStretch() + self.setLayout(layout) \ No newline at end of file diff --git a/qt_ui/windows/mission/QMissionPlanning.py b/qt_ui/windows/mission/QMissionPlanning.py index 30306e2d..15926a84 100644 --- a/qt_ui/windows/mission/QMissionPlanning.py +++ b/qt_ui/windows/mission/QMissionPlanning.py @@ -1,7 +1,7 @@ from PySide2.QtCore import Qt, Slot, QItemSelectionModel, QPoint from PySide2.QtWidgets import QDialog, QGridLayout, QScrollArea, QVBoxLayout, QPushButton from game import Game -from game.event import StrikeEvent, InsurgentAttackEvent, FrontlineAttackEvent, CAP, CAS +from game.event import CAP, CAS, FrontlineAttackEvent from qt_ui.uiconstants import EVENT_ICONS from qt_ui.windows.QWaitingForMissionResultWindow import QWaitingForMissionResultWindow from qt_ui.windows.mission.QPlannedFlightsView import QPlannedFlightsView @@ -18,7 +18,7 @@ class QMissionPlanning(QDialog): self.setMinimumSize(750, 420) self.setModal(True) self.setWindowTitle("Mission Preparation") - self.setWindowIcon(EVENT_ICONS[StrikeEvent]) + self.setWindowIcon(EVENT_ICONS["strike"]) self.init_ui() print("DONE") @@ -84,14 +84,10 @@ class QMissionPlanning(QDialog): # self.game.awacs_expense_commit() #else: # self.gameEvent.is_awacs_enabled = False - self.gameEvent.is_awacs_enabled = False + self.gameEvent.is_awacs_enabled = True self.gameEvent.ca_slots = 1 self.gameEvent.departure_cp = self.game.theater.controlpoints[0] - - if self.game.is_player_attack(self.gameEvent): - self.gameEvent.player_attacking({CAS:{}, CAP:{}}) - else: - self.gameEvent.player_defending({CAS: {}, CAP: {}}) + self.gameEvent.player_attacking({CAS:{}, CAP:{}}) self.gameEvent.depart_from = self.game.theater.controlpoints[0] self.game.initiate_event(self.gameEvent) diff --git a/resources/persiangulf.gif b/resources/persiangulf.gif index 88f74c1a..c7940924 100644 Binary files a/resources/persiangulf.gif and b/resources/persiangulf.gif differ diff --git a/resources/scripts/dcs_liberation.lua b/resources/scripts/dcs_liberation.lua index 3bfc2d29..2b1782cf 100644 --- a/resources/scripts/dcs_liberation.lua +++ b/resources/scripts/dcs_liberation.lua @@ -39,7 +39,7 @@ mist.scheduleFunction(write_state, {}, timer.getTime() + 10, 60, timer.getTime() activeWeapons = {} local function onEvent(event) if event.id == world.event.S_EVENT_CRASH and event.initiator then - messageAll("Destroyed :" .. event.initiator.getName(event.initiator)) + --messageAll("Destroyed :" .. event.initiator.getName(event.initiator)) killed_aircrafts[#killed_aircrafts + 1] = event.initiator.getName(event.initiator) end @@ -52,7 +52,7 @@ local function onEvent(event) end if event.id == world.event.S_EVENT_BASE_CAPTURED and event.place then - messageAll("Base captured :" .. event.place.getName(event.place)) + --messageAll("Base captured :" .. event.place.getName(event.place)) base_capture_events[#base_capture_events + 1] = event.place.getID(event.place) .. "||" .. event.place.getCoalition(event.place) .. "||" .. event.place.getName(event.place) end diff --git a/resources/ui/terrain_pg.gif b/resources/ui/terrain_pg.gif index 4d61b078..0321cc9c 100644 Binary files a/resources/ui/terrain_pg.gif and b/resources/ui/terrain_pg.gif differ diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py deleted file mode 100644 index f84c5836..00000000 --- a/tests/integration/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -from tests.integration import baseattack, convoystrike, frontlineattack, insurgentattack, intercept, navalintercept, strike - -if __name__ == "__main__": - baseattack.execute_all() - convoystrike.execute_all() - frontlineattack.execute_all() - insurgentattack.execute_all() - intercept.execute_all() - navalintercept.execute_all() - strike.execute_all() diff --git a/tests/integration/baseattack.py b/tests/integration/baseattack.py deleted file mode 100644 index 0b867745..00000000 --- a/tests/integration/baseattack.py +++ /dev/null @@ -1,46 +0,0 @@ -from theater.caucasus import CaucasusTheater -from theater.nevada import NevadaTheater - -from tests.integration.util import * - -PLAYER_COUNTRY = "USA" -ENEMY_COUNTRY = "Russia" - - -def execute(game, player_cp, enemy_cp, departure_cp = None): - e = BaseAttackEvent(game, player_cp, enemy_cp, enemy_cp.position, PLAYER_COUNTRY, ENEMY_COUNTRY) - - departures = [departure_cp] if departure_cp else game.theater.player_points() - for departure_cp in departures: - if e.is_departure_available_from(departure_cp): - print("{} for {} ({}) - {}".format(e, player_cp, departure_cp, enemy_cp)) - e.departure_cp = departure_cp - e.player_attacking(autoflights_for(e, PLAYER_COUNTRY)) - - e.generate() - execute_autocommit(e) - e.generate_quick() - execute_autocommit(e) - - -def execute_theater(theater_klass): - print("Theater: {}".format(theater_klass)) - game, theater = init(PLAYER_COUNTRY, ENEMY_COUNTRY, theater_klass) - - total_events = 0 - while len(theater.enemy_points()) > 0: - for player_cp, enemy_cp in theater.conflicts(): - execute(game, player_cp, enemy_cp) - - enemy_cp.captured = True - - print("Total: {}".format(total_events)) - - -def execute_all(): - for theater_klass in [CaucasusTheater, PersianGulfTheater, NevadaTheater]: - execute_theater(theater_klass) - - -if __name__ == "__main__": - execute_all() diff --git a/tests/integration/convoystrike.py b/tests/integration/convoystrike.py deleted file mode 100644 index d690a38c..00000000 --- a/tests/integration/convoystrike.py +++ /dev/null @@ -1,50 +0,0 @@ -from theater.caucasus import CaucasusTheater -from theater.nevada import NevadaTheater - -from tests.integration.util import * - -PLAYER_COUNTRY = "USA" -ENEMY_COUNTRY = "Russia" - - -def execute(game, player_cp, enemy_cp, departure_cp = None): - e = ConvoyStrikeEvent(game, player_cp, enemy_cp, enemy_cp.position, PLAYER_COUNTRY, ENEMY_COUNTRY) - - departures = [departure_cp] if departure_cp else game.theater.player_points() - for departure_cp in departures: - if e.is_departure_available_from(departure_cp): - enemy_cp.base.strength = 1 - for _ in range(10): - print("{} for {} ({}) - {} ({})".format(e, player_cp, departure_cp, enemy_cp, enemy_cp.base.strength)) - e.departure_cp = departure_cp - e.player_attacking(autoflights_for(e, PLAYER_COUNTRY)) - - e.generate() - execute_autocommit(e) - e.generate_quick() - execute_autocommit(e) - - enemy_cp.base.affect_strength(-0.1) - - -def execute_theater(theater_klass): - print("Theater: {}".format(theater_klass)) - game, theater = init(PLAYER_COUNTRY, ENEMY_COUNTRY, theater_klass) - - total_events = 0 - while len(theater.enemy_points()) > 0: - for player_cp, enemy_cp in theater.conflicts(): - execute(game, player_cp, enemy_cp) - - enemy_cp.captured = True - - print("Total: {}".format(total_events)) - - -def execute_all(): - for theater_klass in [CaucasusTheater, PersianGulfTheater, NevadaTheater]: - execute_theater(theater_klass) - - -if __name__ == "__main__": - execute_all() diff --git a/tests/integration/frontlineattack.py b/tests/integration/frontlineattack.py deleted file mode 100644 index 695a76da..00000000 --- a/tests/integration/frontlineattack.py +++ /dev/null @@ -1,52 +0,0 @@ -from theater.caucasus import CaucasusTheater -from theater.nevada import NevadaTheater - -from game.event.frontlineattack import FrontlineAttackEvent - -from tests.integration.util import * - -PLAYER_COUNTRY = "USA" -ENEMY_COUNTRY = "Russia" - - -def execute(game, player_cp, enemy_cp, departure_cp = None): - e = FrontlineAttackEvent(game, player_cp, enemy_cp, enemy_cp.position, PLAYER_COUNTRY, ENEMY_COUNTRY) - - departures = [departure_cp] if departure_cp else game.theater.player_points() - for departure_cp in departures: - if e.is_departure_available_from(departure_cp): - enemy_cp.base.strength = 1 - for _ in range(10): - print("{} for {} ({}) - {} ({})".format(e, player_cp, departure_cp, enemy_cp, enemy_cp.base.strength)) - e.departure_cp = departure_cp - e.player_attacking(autoflights_for(e, PLAYER_COUNTRY)) - - e.generate() - execute_autocommit(e) - e.generate_quick() - execute_autocommit(e) - - enemy_cp.base.affect_strength(-0.1) - - -def execute_theater(theater_klass): - print("Theater: {}".format(theater_klass)) - game, theater = init(PLAYER_COUNTRY, ENEMY_COUNTRY, theater_klass) - - total_events = 0 - while len(theater.enemy_points()) > 0: - for player_cp, enemy_cp in theater.conflicts(): - execute(game, player_cp, enemy_cp) - - enemy_cp.captured = True - - print("Total: {}".format(total_events)) - - -def execute_all(): - for theater_klass in [CaucasusTheater, PersianGulfTheater, NevadaTheater]: - execute_theater(theater_klass) - - -if __name__ == "__main__": - execute_all() diff --git a/tests/integration/insurgentattack.py b/tests/integration/insurgentattack.py deleted file mode 100644 index b996e242..00000000 --- a/tests/integration/insurgentattack.py +++ /dev/null @@ -1,48 +0,0 @@ -from theater.caucasus import CaucasusTheater -from theater.nevada import NevadaTheater - -from game.event.insurgentattack import InsurgentAttackEvent - -from tests.integration.util import * - -PLAYER_COUNTRY = "USA" -ENEMY_COUNTRY = "Russia" - - -def execute(game, player_cp, enemy_cp, departure_cp = None): - e = InsurgentAttackEvent(game, enemy_cp, player_cp, player_cp.position, ENEMY_COUNTRY, PLAYER_COUNTRY) - - departures = [departure_cp] if departure_cp else game.theater.player_points() - for departure_cp in departures: - if e.is_departure_available_from(departure_cp): - print("{} for {} ({}) - {} ({})".format(e, player_cp, departure_cp, enemy_cp, enemy_cp.base.strength)) - e.departure_cp = departure_cp - e.player_defending(autoflights_for(e, PLAYER_COUNTRY)) - - e.generate() - execute_autocommit(e) - e.generate_quick() - execute_autocommit(e) - - -def execute_theater(theater_klass): - print("Theater: {}".format(theater_klass)) - game, theater = init(PLAYER_COUNTRY, ENEMY_COUNTRY, theater_klass) - - total_events = 0 - while len(theater.enemy_points()) > 0: - for player_cp, enemy_cp in theater.conflicts(): - execute(game, player_cp, enemy_cp) - - enemy_cp.captured = True - - print("Total: {}".format(total_events)) - - -def execute_all(): - for theater_klass in [CaucasusTheater, PersianGulfTheater, NevadaTheater]: - execute_theater(theater_klass) - - -if __name__ == "__main__": - execute_all() diff --git a/tests/integration/intercept.py b/tests/integration/intercept.py deleted file mode 100644 index 933c7a7c..00000000 --- a/tests/integration/intercept.py +++ /dev/null @@ -1,48 +0,0 @@ -from theater.caucasus import CaucasusTheater -from theater.nevada import NevadaTheater - -from game.event.intercept import InterceptEvent - -from tests.integration.util import * - -PLAYER_COUNTRY = "USA" -ENEMY_COUNTRY = "Russia" - - -def execute(game, player_cp, enemy_cp, departure_cp = None): - e = InterceptEvent(game, player_cp, enemy_cp, enemy_cp.position, PLAYER_COUNTRY, ENEMY_COUNTRY) - - departures = [departure_cp] if departure_cp else game.theater.player_points() - for departure_cp in departures: - if e.is_departure_available_from(departure_cp): - print("{} for {} ({}) - {} ({})".format(e, player_cp, departure_cp, enemy_cp, enemy_cp.base.strength)) - e.departure_cp = departure_cp - e.player_attacking(autoflights_for(e, PLAYER_COUNTRY)) - - e.generate() - execute_autocommit(e) - e.generate_quick() - execute_autocommit(e) - - -def execute_theater(theater_klass): - print("Theater: {}".format(theater_klass)) - game, theater = init(PLAYER_COUNTRY, ENEMY_COUNTRY, theater_klass) - - total_events = 0 - while len(theater.enemy_points()) > 0: - for player_cp, enemy_cp in theater.conflicts(): - execute(game, player_cp, enemy_cp) - - enemy_cp.captured = True - - print("Total: {}".format(total_events)) - - -def execute_all(): - for theater_klass in [CaucasusTheater, PersianGulfTheater, NevadaTheater]: - execute_theater(theater_klass) - - -if __name__ == "__main__": - execute_all() diff --git a/tests/integration/navalintercept.py b/tests/integration/navalintercept.py deleted file mode 100644 index e2aef1a9..00000000 --- a/tests/integration/navalintercept.py +++ /dev/null @@ -1,49 +0,0 @@ -from theater.caucasus import CaucasusTheater -from theater.nevada import NevadaTheater - -from game.event.intercept import InterceptEvent - -from tests.integration.util import * - -PLAYER_COUNTRY = "USA" -ENEMY_COUNTRY = "Russia" - - -def execute(game, player_cp, enemy_cp, departure_cp = None): - e = NavalInterceptEvent(game, player_cp, enemy_cp, enemy_cp.position, PLAYER_COUNTRY, ENEMY_COUNTRY) - - departures = [departure_cp] if departure_cp else game.theater.player_points() - for departure_cp in departures: - if e.is_departure_available_from(departure_cp): - print("{} for {} ({}) - {} ({})".format(e, player_cp, departure_cp, enemy_cp, enemy_cp.base.strength)) - e.departure_cp = departure_cp - e.player_attacking(autoflights_for(e, PLAYER_COUNTRY)) - - e.generate() - execute_autocommit(e) - e.generate_quick() - execute_autocommit(e) - - -def execute_theater(theater_klass): - print("Theater: {}".format(theater_klass)) - game, theater = init(PLAYER_COUNTRY, ENEMY_COUNTRY, theater_klass) - - total_events = 0 - while len(theater.enemy_points()) > 0: - for player_cp, enemy_cp in theater.conflicts(): - if enemy_cp.radials != LAND: - execute(game, player_cp, enemy_cp) - - enemy_cp.captured = True - - print("Total: {}".format(total_events)) - - -def execute_all(): - for theater_klass in [CaucasusTheater, PersianGulfTheater, NevadaTheater]: - execute_theater(theater_klass) - - -if __name__ == "__main__": - execute_all() diff --git a/tests/integration/strike.py b/tests/integration/strike.py deleted file mode 100644 index 7c6fa190..00000000 --- a/tests/integration/strike.py +++ /dev/null @@ -1,48 +0,0 @@ -from theater.caucasus import CaucasusTheater -from theater.nevada import NevadaTheater - -from game.event.intercept import InterceptEvent - -from tests.integration.util import * - -PLAYER_COUNTRY = "USA" -ENEMY_COUNTRY = "Russia" - - -def execute(game, player_cp, enemy_cp, departure_cp = None): - e = StrikeEvent(game, player_cp, enemy_cp, enemy_cp.position, PLAYER_COUNTRY, ENEMY_COUNTRY) - - departures = [departure_cp] if departure_cp else game.theater.player_points() - for departure_cp in departures: - if e.is_departure_available_from(departure_cp): - print("{} for {} ({}) - {} ({})".format(e, player_cp, departure_cp, enemy_cp, enemy_cp.base.strength)) - e.departure_cp = departure_cp - e.player_attacking(autoflights_for(e, PLAYER_COUNTRY)) - - e.generate() - execute_autocommit(e) - e.generate_quick() - execute_autocommit(e) - - -def execute_theater(theater_klass): - print("Theater: {}".format(theater_klass)) - game, theater = init(PLAYER_COUNTRY, ENEMY_COUNTRY, theater_klass) - - total_events = 0 - while len(theater.enemy_points()) > 0: - for player_cp, enemy_cp in theater.conflicts(): - execute(game, player_cp, enemy_cp) - - enemy_cp.captured = True - - print("Total: {}".format(total_events)) - - -def execute_all(): - for theater_klass in [CaucasusTheater, PersianGulfTheater, NevadaTheater]: - execute_theater(theater_klass) - - -if __name__ == "__main__": - execute_all() diff --git a/tests/integration/util.py b/tests/integration/util.py deleted file mode 100644 index 302ec2cd..00000000 --- a/tests/integration/util.py +++ /dev/null @@ -1,81 +0,0 @@ -from dcs.mission import Mission - -from game import * -from game.event import * -from game.db import * - -from theater.persiangulf import * -from theater import start_generator - -PLAYER_COUNTRY = None -ENEMY_COUNTRY = None - - -def init(player_country: str, enemy_country: str, theater_klass: typing.Type[ConflictTheater]) -> typing.Tuple[Game, ConflictTheater]: - global PLAYER_COUNTRY - global ENEMY_COUNTRY - - PLAYER_COUNTRY = player_country - ENEMY_COUNTRY = enemy_country - - # prerequisites - persistency.setup("./tests/userfolder/") - theater = theater_klass() - start_generator.generate_inital_units(theater, ENEMY_COUNTRY, True, 1) - game = Game(PLAYER_COUNTRY, ENEMY_COUNTRY, theater) - start_generator.generate_groundobjects(theater, game) - return game, theater - - -def autoflights_for(event: Event, country: str) -> TaskForceDict: - result = {} - for task in event.tasks: - result[task] = {find_unittype(task, country)[0]: (1, 1)} - return result - - -class AutodebriefType(Enum): - EVERYONE_DEAD = 0 - PLAYER_DEAD = 1 - ENEMY_DEAD = 2 - - -def autodebrief_for(event: Event, type: AutodebriefType) -> Debriefing: - mission = event.operation.current_mission # type: Mission - - countries = [] - if type == AutodebriefType.PLAYER_DEAD or type == AutodebriefType.EVERYONE_DEAD: - countries.append(mission.country(PLAYER_COUNTRY)) - - if type == AutodebriefType.ENEMY_DEAD or type == AutodebriefType.EVERYONE_DEAD: - countries.append(mission.country(ENEMY_COUNTRY)) - - dead_units = [] - for country in countries: - for group in country.plane_group + country.vehicle_group + country.helicopter_group: - for unit in group.units: - dead_units.append(str(unit.name)) - - return Debriefing(dead_units, [], []) - - -def event_state_save(e: Event) -> typing.Tuple[Base, Base]: - return (copy.deepcopy(e.from_cp.base), copy.deepcopy(e.to_cp.base)) - - -def event_state_restore(e: Event, state: typing.Tuple[Base, Base]): - e.from_cp.base, e.to_cp.base = state[0], state[1] - - -def execute_autocommit(e: Event): - state = event_state_save(e) - e.commit(autodebrief_for(e, AutodebriefType.EVERYONE_DEAD)) - event_state_restore(e, state) - - state = event_state_save(e) - e.commit(autodebrief_for(e, AutodebriefType.PLAYER_DEAD)) - event_state_restore(e, state) - - state = event_state_save(e) - e.commit(autodebrief_for(e, AutodebriefType.ENEMY_DEAD)) - event_state_restore(e, state) diff --git a/tests/userfolder/DCS/Missions/empty.txt b/tests/userfolder/DCS/Missions/empty.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/theater/controlpoint.py b/theater/controlpoint.py index e7aa7d3d..0ffb090e 100644 --- a/theater/controlpoint.py +++ b/theater/controlpoint.py @@ -7,10 +7,10 @@ from dcs.terrain import Airport from dcs.ships import CVN_74_John_C__Stennis, LHA_1_Tarawa, CV_1143_5_Admiral_Kuznetsov from game import db +from gen.ground_forces.combat_stance import CombatStance from .theatergroundobject import TheaterGroundObject - class ControlPointType(Enum): AIRBASE = 0 # An airbase with slot for everything AIRCRAFT_CARRIER_GROUP = 1 # A group with a Stennis type carrier (F/A-18, F-14 compatible) @@ -56,6 +56,7 @@ class ControlPoint: self.connected_points = [] self.base = theater.base.Base() self.cptype = cptype + self.stances = {} @classmethod def from_airport(cls, airport: Airport, radials: typing.Collection[int], size: int, importance: float, has_frontline=True): @@ -75,6 +76,10 @@ class ControlPoint: def is_global(self): return not self.connected_points + @property + def is_carrier(self): + return self.cptype in [ControlPointType.AIRCRAFT_CARRIER_GROUP, ControlPointType.LHA_GROUP] + @property def sea_radials(self) -> typing.Collection[int]: # TODO: fix imports @@ -87,6 +92,7 @@ class ControlPoint: def connect(self, to): self.connected_points.append(to) + self.stances[to.id] = CombatStance.DEFENSIVE def has_runway(self): """ diff --git a/theater/persiangulf.py b/theater/persiangulf.py index 4fa9d541..33f549e3 100644 --- a/theater/persiangulf.py +++ b/theater/persiangulf.py @@ -9,8 +9,9 @@ from .landmap import load_landmap class PersianGulfTheater(ConflictTheater): terrain = dcs.terrain.PersianGulf() overview_image = "persiangulf.gif" - reference_points = {(persiangulf.Sir_Abu_Nuayr.position.x, persiangulf.Sir_Abu_Nuayr.position.y): (321*4, 145*4), - (persiangulf.Sirri_Island.position.x, persiangulf.Sirri_Island.position.y): (347*4, 82*4), } + reference_points = { + (persiangulf.Sir_Abu_Nuayr.position.x, persiangulf.Sir_Abu_Nuayr.position.y): (423 * 4, 150 * 4), + (persiangulf.Sirri_Island.position.x, persiangulf.Sirri_Island.position.y): (447 * 4, 82 * 4), } landmap = load_landmap("resources\\gulflandmap.p") daytime_map = { "dawn": (6, 8), @@ -94,8 +95,8 @@ class IranianCampaign(ConflictTheater): terrain = dcs.terrain.PersianGulf() overview_image = "persiangulf.gif" - reference_points = {(persiangulf.Sir_Abu_Nuayr.position.x, persiangulf.Sir_Abu_Nuayr.position.y): (321*4, 145*4), - (persiangulf.Sirri_Island.position.x, persiangulf.Sirri_Island.position.y): (347*4, 82*4), } + reference_points = {(persiangulf.Sir_Abu_Nuayr.position.x, persiangulf.Sir_Abu_Nuayr.position.y): (423*4, 150*4), + (persiangulf.Sirri_Island.position.x, persiangulf.Sirri_Island.position.y): (447*4, 82*4), } landmap = load_landmap("resources\\gulflandmap.p") daytime_map = { "dawn": (6, 8), diff --git a/theater/theatergroundobject.py b/theater/theatergroundobject.py index 5e7fb5d6..1dbd1fad 100644 --- a/theater/theatergroundobject.py +++ b/theater/theatergroundobject.py @@ -29,7 +29,6 @@ ABBREV_NAME = { "oil": "OILP" } - CATEGORY_MAP = { "CARRIER": ["CARRIER"], "aa": ["AA"],