diff --git a/game/__init__.py b/game/__init__.py index e69de29b..126a7d24 100644 --- a/game/__init__.py +++ b/game/__init__.py @@ -0,0 +1,2 @@ +from .game import Game +from . import db \ No newline at end of file diff --git a/game/db.py b/game/db.py index ee798658..20b1a091 100644 --- a/game/db.py +++ b/game/db.py @@ -71,6 +71,9 @@ PRICES = { # ship CV_1143_5_Admiral_Kuznetsov: 100, CVN_74_John_C__Stennis: 100, + + Bulk_cargo_ship_Yakushev: 100, + Dry_cargo_ship_Ivanov: 100, } UNIT_BY_TASK = { @@ -130,7 +133,9 @@ UNIT_BY_TASK = { AirDefence.SAM_SA_19_Tunguska_2S6, AirDefence.SAM_SA_8_Osa_9A33, ], + Carriage: [CVN_74_John_C__Stennis, CV_1143_5_Admiral_Kuznetsov, ], + CargoTransportation: [Dry_cargo_ship_Ivanov, Bulk_cargo_ship_Yakushev], } SAM_BAN = [ @@ -185,7 +190,10 @@ UNIT_BY_COUNTRY = { Armor.MBT_T_90, Armor.MBT_T_80U, Armor.MBT_T_55, - CV_1143_5_Admiral_Kuznetsov], + CV_1143_5_Admiral_Kuznetsov, + Bulk_cargo_ship_Yakushev, + Dry_cargo_ship_Ivanov, + ], "USA": [ F_15C, @@ -212,6 +220,9 @@ UNIT_BY_COUNTRY = { AirDefence.SAM_Patriot_ICC, CVN_74_John_C__Stennis, + # TODO: verify or find out proper USA cargo ship + Bulk_cargo_ship_Yakushev, + Dry_cargo_ship_Ivanov, ], } @@ -220,6 +231,7 @@ PLANE_PAYLOAD_OVERRIDES = { "*": "AIM-9M*6, AIM-7M*2, FUEL*3", }, + # TODO: figure out a way to setup su33 loadout Su_33: FighterSweep, M_2000C: { @@ -238,6 +250,7 @@ PLANE_LIVERY_OVERRIDES = { UnitsDict = typing.Dict[UnitType, int] PlaneDict = typing.Dict[FlyingType, int] ArmorDict = typing.Dict[VehicleType, int] +ShipDict = typing.Dict[ShipType, int] AirDefenseDict = typing.Dict[AirDefence, int] StartingPosition = typing.Optional[typing.Union[ShipGroup, Airport, Point]] diff --git a/game/event.py b/game/event.py deleted file mode 100644 index 45a03416..00000000 --- a/game/event.py +++ /dev/null @@ -1,320 +0,0 @@ -from game.operation import * - -DIFFICULTY_LOG_BASE = 1.5 - - -class Event: - silent = False - informational = False - is_awacs_enabled = False - operation = None # type: Operation - difficulty = 1 # type: int - BONUS_BASE = 0 - - def __init__(self, attacker_name: str, defender_name: str, from_cp: ControlPoint, to_cp: ControlPoint, game): - self.attacker_name = attacker_name - self.defender_name = defender_name - self.to_cp = to_cp - self.from_cp = from_cp - self.game = game - - @property - def is_player_attacking(self) -> bool: - return self.attacker_name == self.game.player - - @property - def enemy_cp(self) -> ControlPoint: - if self.attacker_name == self.game.player: - return self.to_cp - else: - return self.from_cp - - @property - def threat_description(self) -> str: - return "" - - def bonus(self) -> int: - return math.ceil(math.log(self.difficulty, DIFFICULTY_LOG_BASE) * self.BONUS_BASE) - - def is_successfull(self, debriefing: Debriefing) -> bool: - return self.operation.is_successfull(debriefing) - - def generate(self): - self.operation.is_awacs_enabled = self.is_awacs_enabled - self.operation.prepare(self.game.theater.terrain, is_quick=False) - self.operation.generate() - self.operation.mission.save("build/nextturn.miz") - - def generate_quick(self): - self.operation.is_awacs_enabled = self.is_awacs_enabled - self.operation.prepare(self.game.theater.terrain, is_quick=True) - self.operation.generate() - self.operation.mission.save('build/nextturn_quick.miz') - - def commit(self, debriefing: Debriefing): - for country, losses in debriefing.destroyed_units.items(): - if country == self.attacker_name: - cp = self.from_cp - else: - cp = self.to_cp - - cp.base.commit_losses(losses) - - def skip(self): - pass - - -class GroundInterceptEvent(Event): - BONUS_BASE = 3 - TARGET_AMOUNT_FACTOR = 2 - TARGET_VARIETY = 2 - STRENGTH_INFLUENCE = 0.3 - SUCCESS_TARGETS_HIT_PERCENTAGE = 0.5 - - targets = None # type: db.ArmorDict - - def __str__(self): - return "Ground intercept from {} at {}".format(self.from_cp, self.to_cp) - - def is_successfull(self, debriefing: Debriefing): - total_targets = sum(self.targets.values()) - destroyed_targets = 0 - for unit, count in debriefing.destroyed_units[self.defender_name].items(): - if unit in self.targets: - destroyed_targets += count - - return (float(destroyed_targets) / float(total_targets)) >= self.SUCCESS_TARGETS_HIT_PERCENTAGE - - def commit(self, debriefing: Debriefing): - super(GroundInterceptEvent, 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: - assert False - - def skip(self): - if not self.to_cp.captured: - self.to_cp.base.affect_strength(+0.1) - else: - pass - - def player_attacking(self, strikegroup: db.PlaneDict, clients: db.PlaneDict): - suitable_unittypes = db.find_unittype(PinpointStrike, self.defender_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 = GroundInterceptOperation(game=self.game, - attacker_name=self.attacker_name, - defender_name=self.defender_name, - attacker_clients=clients, - defender_clients={}, - from_cp=self.from_cp, - to_cp=self.to_cp) - op.setup(target=self.targets, - strikegroup=strikegroup) - - self.operation = op - - -class InterceptEvent(Event): - BONUS_BASE = 5 - STRENGTH_INFLUENCE = 0.3 - GLOBAL_STRENGTH_INFLUENCE = 0.3 - AIRDEFENSE_COUNT = 3 - - transport_unit = None # type: FlyingType - - def __str__(self): - return "Intercept from {} at {}".format(self.from_cp, self.to_cp) - - @property - def threat_description(self): - return "{} aircraft".format(self.enemy_cp.base.scramble_count()) - - def is_successfull(self, debriefing: Debriefing): - units_destroyed = debriefing.destroyed_units[self.defender_name].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: - if self.is_successfull(debriefing): - self.to_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, interceptors: db.PlaneDict, clients: db.PlaneDict): - escort = self.to_cp.base.scramble_sweep() - - 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, - attacker_clients=clients, - defender_clients={}, - from_cp=self.from_cp, - to_cp=self.to_cp) - - op.setup(escort=escort, - transport={self.transport_unit: 1}, - airdefense={airdefense_unit: self.AIRDEFENSE_COUNT}, - interceptors=interceptors) - - self.operation = op - - def player_defending(self, escort: db.PlaneDict, clients: db.PlaneDict): - interceptors = self.from_cp.base.scramble_interceptors() - - 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, - attacker_clients={}, - defender_clients=clients, - from_cp=self.from_cp, - to_cp=self.to_cp) - - op.setup(escort=escort, - transport={self.transport_unit: 1}, - interceptors=interceptors, - airdefense={}) - - self.operation = op - - -class CaptureEvent(Event): - silent = True - BONUS_BASE = 7 - STRENGTH_RECOVERY = 0.35 - - def __str__(self): - return "Attack from {} to {}".format(self.from_cp, self.to_cp) - - @property - def threat_description(self): - descr = "{} aircraft + CAS, {} vehicles".format( - self.enemy_cp.base.scramble_count(), - self.enemy_cp.base.assemble_count() - ) - - if self.is_player_attacking: - descr += ", {} AA".format(self.enemy_cp.base.assemble_aa_count()) - - return descr - - def is_successfull(self, debriefing: Debriefing): - alive_attackers = sum(debriefing.alive_units[self.attacker_name].values()) - alive_defenders = sum(debriefing.alive_units[self.defender_name].values()) - attackers_success = alive_attackers > alive_defenders - if self.from_cp.captured: - return attackers_success - else: - return not attackers_success - - def commit(self, debriefing: Debriefing): - super(CaptureEvent, self).commit(debriefing) - if self.is_successfull(debriefing): - if self.from_cp.captured: - self.to_cp.captured = True - self.to_cp.base.filter_units(db.UNIT_BY_COUNTRY[self.attacker_name]) - - self.to_cp.base.affect_strength(+self.STRENGTH_RECOVERY) - else: - if not self.from_cp.captured: - self.to_cp.captured = False - self.to_cp.base.affect_strength(+self.STRENGTH_RECOVERY) - - def skip(self): - if self.to_cp.captured: - self.to_cp.captured = False - - def player_defending(self, interceptors: db.PlaneDict, clients: db.PlaneDict): - cas = self.from_cp.base.scramble_cas() - escort = self.from_cp.base.scramble_sweep() - attackers = self.from_cp.base.assemble_cap() - - op = CaptureOperation(game=self.game, - attacker_name=self.attacker_name, - defender_name=self.defender_name, - attacker_clients={}, - defender_clients=clients, - from_cp=self.from_cp, - to_cp=self.to_cp) - - op.setup(cas=cas, - escort=escort, - attack=attackers, - intercept=interceptors, - defense=self.to_cp.base.armor, - aa=self.to_cp.base.aa) - - self.operation = op - - def player_attacking(self, cas: db.PlaneDict, escort: db.PlaneDict, armor: db.ArmorDict, clients: db.PlaneDict): - interceptors = self.to_cp.base.scramble_sweep() - - op = CaptureOperation(game=self.game, - attacker_name=self.attacker_name, - defender_name=self.defender_name, - attacker_clients=clients, - defender_clients={}, - from_cp=self.from_cp, - to_cp=self.to_cp) - - op.setup(cas=cas, - escort=escort, - attack=armor, - intercept=interceptors, - defense=self.to_cp.base.armor, - aa=self.to_cp.base.assemble_aa()) - - self.operation = op - - -class UnitsDeliveryEvent(Event): - informational = True - units = None # type: typing.Dict[UnitType, int] - - def __init__(self, attacker_name: str, defender_name: str, from_cp: ControlPoint, to_cp: ControlPoint, game): - super(UnitsDeliveryEvent, self).__init__(attacker_name=attacker_name, - defender_name=defender_name, - from_cp=from_cp, - to_cp=to_cp, - game=game) - - self.units = {} - - def __str__(self): - return "Pending delivery to {}".format(self.to_cp) - - def deliver(self, units: typing.Dict[UnitType, int]): - for k, v in units.items(): - self.units[k] = self.units.get(k, 0) + v - - def skip(self): - self.to_cp.base.commision_units(self.units) diff --git a/game/event/__init__.py b/game/event/__init__.py new file mode 100644 index 00000000..9f5fe917 --- /dev/null +++ b/game/event/__init__.py @@ -0,0 +1,5 @@ +from .event import * +from .groundintercept import * +from .intercept import * +from .capture import * +from .navalintercept import * diff --git a/game/event/capture.py b/game/event/capture.py new file mode 100644 index 00000000..c1f44283 --- /dev/null +++ b/game/event/capture.py @@ -0,0 +1,98 @@ +import math +import random + +from game import db +from game.operation.capture import CaptureOperation +from userdata.debriefing import Debriefing + +from .event import Event + + +class CaptureEvent(Event): + silent = True + BONUS_BASE = 7 + STRENGTH_RECOVERY = 0.35 + + def __str__(self): + return "Attack from {} to {}".format(self.from_cp, self.to_cp) + + @property + def threat_description(self): + descr = "{} aircraft + CAS, {} vehicles".format( + self.enemy_cp.base.scramble_count(), + self.enemy_cp.base.assemble_count() + ) + + if self.is_player_attacking: + descr += ", {} AA".format(self.enemy_cp.base.assemble_aa_count()) + + return descr + + def is_successfull(self, debriefing: Debriefing): + alive_attackers = sum(debriefing.alive_units[self.attacker_name].values()) + alive_defenders = sum(debriefing.alive_units[self.defender_name].values()) + attackers_success = alive_attackers > alive_defenders + if self.from_cp.captured: + return attackers_success + else: + return not attackers_success + + def commit(self, debriefing: Debriefing): + super(CaptureEvent, self).commit(debriefing) + if self.is_successfull(debriefing): + if self.from_cp.captured: + self.to_cp.captured = True + self.to_cp.base.filter_units(db.UNIT_BY_COUNTRY[self.attacker_name]) + + self.to_cp.base.affect_strength(+self.STRENGTH_RECOVERY) + else: + if not self.from_cp.captured: + self.to_cp.captured = False + self.to_cp.base.affect_strength(+self.STRENGTH_RECOVERY) + + def skip(self): + if self.to_cp.captured: + self.to_cp.captured = False + + def player_defending(self, interceptors: db.PlaneDict, clients: db.PlaneDict): + cas = self.from_cp.base.scramble_cas() + escort = self.from_cp.base.scramble_sweep() + attackers = self.from_cp.base.assemble_cap() + + op = CaptureOperation(game=self.game, + attacker_name=self.attacker_name, + defender_name=self.defender_name, + attacker_clients={}, + defender_clients=clients, + from_cp=self.from_cp, + to_cp=self.to_cp) + + op.setup(cas=cas, + escort=escort, + attack=attackers, + intercept=interceptors, + defense=self.to_cp.base.armor, + aa=self.to_cp.base.aa) + + self.operation = op + + def player_attacking(self, cas: db.PlaneDict, escort: db.PlaneDict, armor: db.ArmorDict, clients: db.PlaneDict): + interceptors = self.to_cp.base.scramble_sweep() + + op = CaptureOperation(game=self.game, + attacker_name=self.attacker_name, + defender_name=self.defender_name, + attacker_clients=clients, + defender_clients={}, + from_cp=self.from_cp, + to_cp=self.to_cp) + + op.setup(cas=cas, + escort=escort, + attack=armor, + intercept=interceptors, + defense=self.to_cp.base.armor, + aa=self.to_cp.base.assemble_aa()) + + self.operation = op + diff --git a/game/event/event.py b/game/event/event.py new file mode 100644 index 00000000..f3bc78e7 --- /dev/null +++ b/game/event/event.py @@ -0,0 +1,93 @@ +from dcs.unittype import UnitType + +from game import * +from theater import * + +from userdata.debriefing import Debriefing + +DIFFICULTY_LOG_BASE = 1.5 + + +class Event: + silent = False + informational = False + is_awacs_enabled = False + operation = None # type: Operation + difficulty = 1 # type: int + BONUS_BASE = 0 + + def __init__(self, attacker_name: str, defender_name: str, from_cp: ControlPoint, to_cp: ControlPoint, game): + self.attacker_name = attacker_name + self.defender_name = defender_name + self.to_cp = to_cp + self.from_cp = from_cp + self.game = game + + @property + def is_player_attacking(self) -> bool: + return self.attacker_name == self.game.player + + @property + def enemy_cp(self) -> ControlPoint: + if self.attacker_name == self.game.player: + return self.to_cp + else: + return self.from_cp + + @property + def threat_description(self) -> str: + return "" + + def bonus(self) -> int: + return math.ceil(math.log(self.difficulty, DIFFICULTY_LOG_BASE) * self.BONUS_BASE) + + def is_successfull(self, debriefing: Debriefing) -> bool: + return self.operation.is_successfull(debriefing) + + def generate(self): + self.operation.is_awacs_enabled = self.is_awacs_enabled + self.operation.prepare(self.game.theater.terrain, is_quick=False) + self.operation.generate() + self.operation.mission.save("build/nextturn.miz") + + def generate_quick(self): + self.operation.is_awacs_enabled = self.is_awacs_enabled + self.operation.prepare(self.game.theater.terrain, is_quick=True) + self.operation.generate() + self.operation.mission.save('build/nextturn_quick.miz') + + def commit(self, debriefing: Debriefing): + for country, losses in debriefing.destroyed_units.items(): + if country == self.attacker_name: + cp = self.from_cp + else: + cp = self.to_cp + + cp.base.commit_losses(losses) + + def skip(self): + pass + + +class UnitsDeliveryEvent(Event): + informational = True + units = None # type: typing.Dict[UnitType, int] + + def __init__(self, attacker_name: str, defender_name: str, from_cp: ControlPoint, to_cp: ControlPoint, game): + super(UnitsDeliveryEvent, self).__init__(attacker_name=attacker_name, + defender_name=defender_name, + from_cp=from_cp, + to_cp=to_cp, + game=game) + + self.units = {} + + def __str__(self): + return "Pending delivery to {}".format(self.to_cp) + + def deliver(self, units: typing.Dict[UnitType, int]): + for k, v in units.items(): + self.units[k] = self.units.get(k, 0) + v + + def skip(self): + self.to_cp.base.commision_units(self.units) diff --git a/game/event/groundintercept.py b/game/event/groundintercept.py new file mode 100644 index 00000000..2e5f1814 --- /dev/null +++ b/game/event/groundintercept.py @@ -0,0 +1,68 @@ +import math +import random + +from dcs.task import * + +from game import * +from game.event import * +from userdata.debriefing import Debriefing + + +class GroundInterceptEvent(Event): + BONUS_BASE = 3 + TARGET_AMOUNT_FACTOR = 2 + TARGET_VARIETY = 2 + STRENGTH_INFLUENCE = 0.3 + SUCCESS_TARGETS_HIT_PERCENTAGE = 0.5 + + targets = None # type: db.ArmorDict + + def __str__(self): + return "Ground intercept from {} at {}".format(self.from_cp, self.to_cp) + + def is_successfull(self, debriefing: Debriefing): + total_targets = sum(self.targets.values()) + destroyed_targets = 0 + for unit, count in debriefing.destroyed_units[self.defender_name].items(): + if unit in self.targets: + destroyed_targets += count + + return (float(destroyed_targets) / float(total_targets)) >= self.SUCCESS_TARGETS_HIT_PERCENTAGE + + def commit(self, debriefing: Debriefing): + super(GroundInterceptEvent, 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: + assert False + + def skip(self): + if not self.to_cp.captured: + self.to_cp.base.affect_strength(+0.1) + else: + pass + + def player_attacking(self, strikegroup: db.PlaneDict, clients: db.PlaneDict): + suitable_unittypes = db.find_unittype(PinpointStrike, self.defender_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 = GroundInterceptOperation(game=self.game, + attacker_name=self.attacker_name, + defender_name=self.defender_name, + attacker_clients=clients, + defender_clients={}, + from_cp=self.from_cp, + to_cp=self.to_cp) + op.setup(target=self.targets, + strikegroup=strikegroup) + + self.operation = op + + diff --git a/game/event/intercept.py b/game/event/intercept.py new file mode 100644 index 00000000..ea95757c --- /dev/null +++ b/game/event/intercept.py @@ -0,0 +1,98 @@ +import math +import random + +from dcs.task import * +from dcs.vehicles import * + +from game import db +from game.operation.intercept import InterceptOperation +from userdata.debriefing import Debriefing + +from .event import Event + + +class InterceptEvent(Event): + BONUS_BASE = 5 + STRENGTH_INFLUENCE = 0.3 + GLOBAL_STRENGTH_INFLUENCE = 0.3 + AIRDEFENSE_COUNT = 3 + + transport_unit = None # type: FlyingType + + def __str__(self): + return "Intercept from {} at {}".format(self.from_cp, self.to_cp) + + @property + def threat_description(self): + return "{} aircraft".format(self.enemy_cp.base.scramble_count()) + + def is_successfull(self, debriefing: Debriefing): + units_destroyed = debriefing.destroyed_units[self.defender_name].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: + if self.is_successfull(debriefing): + self.to_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, interceptors: db.PlaneDict, clients: db.PlaneDict): + escort = self.to_cp.base.scramble_sweep() + + 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, + attacker_clients=clients, + defender_clients={}, + from_cp=self.from_cp, + to_cp=self.to_cp) + + op.setup(escort=escort, + transport={self.transport_unit: 1}, + airdefense={airdefense_unit: self.AIRDEFENSE_COUNT}, + interceptors=interceptors) + + self.operation = op + + def player_defending(self, escort: db.PlaneDict, clients: db.PlaneDict): + interceptors = self.from_cp.base.scramble_interceptors() + + 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, + attacker_clients={}, + defender_clients=clients, + from_cp=self.from_cp, + to_cp=self.to_cp) + + op.setup(escort=escort, + transport={self.transport_unit: 1}, + interceptors=interceptors, + airdefense={}) + + self.operation = op + + diff --git a/game/event/navalintercept.py b/game/event/navalintercept.py new file mode 100644 index 00000000..95bea82c --- /dev/null +++ b/game/event/navalintercept.py @@ -0,0 +1,102 @@ +import typing +import math +import random + +from dcs.task import * +from dcs.vehicles import * + +from game import db +from game.operation.navalintercept import NavalInterceptionOperation +from userdata.debriefing import Debriefing + +from .event import Event + + +class NavalInterceptEvent(Event): + STRENGTH_INFLUENCE = 0.3 + + targets = None # type: db.ShipDict + + def _targets_count(self) -> int: + from gen.conflictgen import IMPORTANCE_LOW, IMPORTANCE_HIGH + factor = (self.to_cp.importance - IMPORTANCE_LOW) * 10 + return min(int(factor), 1) + + def __str__(self) -> str: + return "Naval intercept at {}".format(self.to_cp) + + @property + def threat_description(self): + s = "{} ship(s)".format(self._targets_count()) + if not self.from_cp.captured: + s += ", {} aircraft".format(self.from_cp.base.scramble_count()) + return s + + def is_successfull(self, debriefing: Debriefing): + targets_destroyed = [c for t, c in debriefing.destroyed_units.items() if t in self.targets.values()] + if self.from_cp.captured: + return targets_destroyed > 0 + else: + return targets_destroyed == 0 + + def commit(self, debriefing: Debriefing): + super(NavalInterceptEvent, self).commit(debriefing) + + if self.attacker_name == self.game.player: + if self.is_successfull(debriefing): + self.to_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, strikegroup: db.PlaneDict, clients: db.PlaneDict): + 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, + attacker_clients=clients, + defender_clients={}, + from_cp=self.from_cp, + to_cp=self.to_cp + ) + + op.setup(strikegroup=strikegroup, + interceptors={}, + targets=self.targets) + + self.operation = op + + def player_defending(self, interceptors: db.PlaneDict, clients: db.PlaneDict): + 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, + attacker_clients=clients, + defender_clients={}, + from_cp=self.from_cp, + to_cp=self.to_cp + ) + + strikegroup = self.from_cp.base.scramble_cas() + op.setup(strikegroup=strikegroup, + interceptors=interceptors, + targets=self.targets) + + self.operation = op diff --git a/game/event_results.py b/game/event_results.py deleted file mode 100644 index 55a78829..00000000 --- a/game/event_results.py +++ /dev/null @@ -1,8 +0,0 @@ -import typing -import dcs - -from game.event import * - - - - diff --git a/game/game.py b/game/game.py index f1f8eb25..76c6a3f5 100644 --- a/game/game.py +++ b/game/game.py @@ -1,4 +1,15 @@ -from game.event import * +import typing +import random +import math + +from dcs.task import * +from dcs.vehicles import * + +from userdata.debriefing import Debriefing +from theater import * + +from . import db +from .event import * COMMISION_LIMITS_SCALE = 2 COMMISION_LIMITS_FACTORS = { @@ -124,6 +135,19 @@ class Game: game=self)) break + def _generate_navalinterceptions(self): + for from_cp, to_cp in self.theater.conflicts(True): + if to_cp.radials == ALL_RADIALS: + continue + + if self._roll(100, from_cp.base.strength): + self.events.append(NavalInterceptEvent(attacker_name=self.player, + defender_name=self.enemy, + from_cp=from_cp, + to_cp=to_cp, + game=self)) + break + def _generate_globalinterceptions(self): global_count = len([x for x in self.theater.player_points() if x.is_global]) for from_cp in [x for x in self.theater.player_points() if x.is_global]: @@ -205,8 +229,9 @@ class Game: self.events = [] # type: typing.List[Event] self._fill_cap_events() - self._generate_enemy_caps(ignored_cps=ignored_cps) + #self._generate_enemy_caps(ignored_cps=ignored_cps) self._generate_interceptions() self._generate_globalinterceptions() self._generate_groundinterceptions() + self._generate_navalinterceptions() diff --git a/game/operation.py b/game/operation.py deleted file mode 100644 index d86b9995..00000000 --- a/game/operation.py +++ /dev/null @@ -1,221 +0,0 @@ -from userdata.debriefing import * -from theater.conflicttheater import * -from theater.base import * - -from gen.armor import * -from gen.aircraft import * -from gen.aaa import * -from gen.shipgen import * -from gen.conflictgen import * -from gen.settingsgen import * -from gen.awacsgen import * -from gen.visualgen import * - - -class Operation: - attackers_starting_position = None # type: db.StartingPosition - defenders_starting_position = None # type: db.StartingPosition - mission = None # type: dcs.Mission - conflict = None # type: Conflict - armorgen = None # type: ArmorConflictGenerator - airgen = None # type: AircraftConflictGenerator - aagen = None # type: AAConflictGenerator - extra_aagen = None # type: ExtraAAConflictGenerator - shipgen = None # type: ShipGenerator - envgen = None # type: SettingsGenerator - awacsgen = None # type: AWACSConflictGenerator - visualgen = None # type: VisualGenerator - - is_awacs_enabled = False - - def __init__(self, - game, - attacker_name: str, - defender_name: str, - attacker_clients: db.PlaneDict, - defender_clients: db.PlaneDict, - from_cp: ControlPoint, - to_cp: ControlPoint = None): - self.game = game - self.attacker_name = attacker_name - self.defender_name = defender_name - self.attacker_clients = attacker_clients - self.defender_clients = defender_clients - self.from_cp = from_cp - self.to_cp = to_cp - self.is_quick = False - - def initialize(self, mission: Mission, conflict: Conflict): - self.mission = mission - self.conflict = conflict - - self.armorgen = ArmorConflictGenerator(mission, conflict) - self.airgen = AircraftConflictGenerator(mission, conflict) - self.aagen = AAConflictGenerator(mission, conflict) - self.shipgen = ShipGenerator(mission, conflict) - self.awacsgen = AWACSConflictGenerator(mission, conflict, self.game) - self.envgen = SettingsGenerator(mission, conflict, self.game) - self.visualgen = VisualGenerator(mission, conflict, self.game) - - player_name = self.from_cp.captured and self.attacker_name or self.defender_name - enemy_name = self.from_cp.captured and self.defender_name or self.attacker_name - self.extra_aagen = ExtraAAConflictGenerator(mission, conflict, self.game, player_name, enemy_name) - - def prepare(self, terrain: dcs.terrain.Terrain, is_quick: bool): - self.mission = dcs.Mission(terrain) - self.is_quick = is_quick - - if is_quick: - self.attackers_starting_position = None - self.defenders_starting_position = None - else: - self.attackers_starting_position = self.from_cp.at - self.defenders_starting_position = self.to_cp.at - - def generate(self): - self.visualgen.generate() - - if self.is_awacs_enabled: - self.awacsgen.generate() - - self.extra_aagen.generate() - self.envgen.generate(self.is_quick) - - for global_cp in self.game.theater.controlpoints: - if not global_cp.is_global: - continue - - ship = self.shipgen.generate(type=db.find_unittype(Carriage, self.game.player)[0], - country=self.game.player, - at=global_cp.at) - - if global_cp == self.from_cp and not self.is_quick: - self.attackers_starting_position = ship - - def units_of(self, country_name: str) -> typing.Collection[UnitType]: - return [] - - def is_successfull(self, debriefing: Debriefing) -> bool: - return True - - -class CaptureOperation(Operation): - cas = None # type: db.PlaneDict - escort = None # type: db.PlaneDict - intercept = None # type: db.PlaneDict - attack = None # type: db.ArmorDict - defense = None # type: db.ArmorDict - aa = None # type: db.AirDefenseDict - - def setup(self, - cas: db.PlaneDict, - escort: db.PlaneDict, - attack: db.ArmorDict, - intercept: db.PlaneDict, - 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(CaptureOperation, self).prepare(terrain, is_quick) - - self.defenders_starting_position = None - if self.game.player == self.defender_name: - self.attackers_starting_position = None - - conflict = Conflict.capture_conflict( - attacker=self.mission.country(self.attacker_name), - defender=self.mission.country(self.defender_name), - from_cp=self.from_cp, - to_cp=self.to_cp - ) - self.initialize(mission=self.mission, - conflict=conflict) - - def generate(self): - self.armorgen.generate(self.attack, self.defense) - self.aagen.generate(self.aa) - - self.airgen.generate_defense(self.intercept, clients=self.defender_clients, at=self.defenders_starting_position) - - self.airgen.generate_cas(self.cas, clients=self.attacker_clients, at=self.attackers_starting_position) - self.airgen.generate_cas_escort(self.escort, clients=self.attacker_clients, at=self.attackers_starting_position) - - self.visualgen.generate_target_smokes(self.to_cp) - super(CaptureOperation, self).generate() - - -class InterceptOperation(Operation): - escort = None # type: db.PlaneDict - transport = None # type: db.PlaneDict - interceptors = None # type: db.PlaneDict - airdefense = None # type: db.AirDefenseDict - - def setup(self, - escort: db.PlaneDict, - transport: db.PlaneDict, - airdefense: db.AirDefenseDict, - interceptors: db.PlaneDict): - self.escort = escort - self.transport = transport - self.airdefense = airdefense - self.interceptors = interceptors - - def prepare(self, terrain: dcs.terrain.Terrain, is_quick: bool): - super(InterceptOperation, self).prepare(terrain, is_quick) - self.defenders_starting_position = None - if self.defender_name == self.game.player: - self.attackers_starting_position = None - - conflict = Conflict.intercept_conflict( - attacker=self.mission.country(self.attacker_name), - defender=self.mission.country(self.defender_name), - from_cp=self.from_cp, - to_cp=self.to_cp - ) - - self.initialize(mission=self.mission, - conflict=conflict) - - def generate(self): - self.airgen.generate_transport(self.transport, self.to_cp.at) - self.airgen.generate_transport_escort(self.escort, clients=self.defender_clients) - - if self.from_cp.is_global: - super(InterceptOperation, self).generate() - self.airgen.generate_interception(self.interceptors, clients=self.attacker_clients, at=self.attackers_starting_position) - else: - self.airgen.generate_interception(self.interceptors, clients=self.attacker_clients, at=self.attackers_starting_position) - super(InterceptOperation, self).generate() - - -class GroundInterceptOperation(Operation): - def setup(self, - target: db.ArmorDict, - strikegroup: db.PlaneDict): - self.strikegroup = strikegroup - self.target = target - - def prepare(self, terrain: dcs.terrain.Terrain, is_quick: bool): - super(GroundInterceptOperation, self).prepare(terrain, is_quick) - conflict = Conflict.ground_intercept_conflict( - attacker=self.mission.country(self.attacker_name), - defender=self.mission.country(self.defender_name), - heading=self.to_cp.position.heading_between_point(self.from_cp.position), - from_cp=self.from_cp, - to_cp=self.to_cp - ) - - self.initialize(mission=self.mission, - conflict=conflict) - - def generate(self): - self.airgen.generate_cas(self.strikegroup, clients=self.attacker_clients, at=self.attackers_starting_position) - self.armorgen.generate({}, self.target) - - super(GroundInterceptOperation, self).generate() diff --git a/game/operation/__init__.py b/game/operation/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/game/operation/capture.py b/game/operation/capture.py new file mode 100644 index 00000000..20eb24a0 --- /dev/null +++ b/game/operation/capture.py @@ -0,0 +1,63 @@ +from game import db + +from gen.conflictgen import Conflict +from gen.armor import * +from gen.aircraft import * +from gen.aaa import * +from gen.shipgen import * +from gen.settingsgen import * +from gen.awacsgen import * +from gen.visualgen import * + +from .operation import Operation + +class CaptureOperation(Operation): + cas = None # type: db.PlaneDict + escort = None # type: db.PlaneDict + intercept = None # type: db.PlaneDict + attack = None # type: db.ArmorDict + defense = None # type: db.ArmorDict + aa = None # type: db.AirDefenseDict + + def setup(self, + cas: db.PlaneDict, + escort: db.PlaneDict, + attack: db.ArmorDict, + intercept: db.PlaneDict, + 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(CaptureOperation, self).prepare(terrain, is_quick) + + self.defenders_starting_position = None + if self.game.player == self.defender_name: + self.attackers_starting_position = None + + conflict = Conflict.capture_conflict( + attacker=self.mission.country(self.attacker_name), + defender=self.mission.country(self.defender_name), + from_cp=self.from_cp, + to_cp=self.to_cp + ) + self.initialize(mission=self.mission, + conflict=conflict) + + def generate(self): + self.armorgen.generate(self.attack, self.defense) + self.aagen.generate(self.aa) + + self.airgen.generate_defense(self.intercept, clients=self.defender_clients, at=self.defenders_starting_position) + + self.airgen.generate_cas_strikegroup(self.cas, clients=self.attacker_clients, at=self.attackers_starting_position) + self.airgen.generate_strikegroup_escort(self.escort, clients=self.attacker_clients, at=self.attackers_starting_position) + + self.visualgen.generate_target_smokes(self.to_cp) + super(CaptureOperation, self).generate() + diff --git a/game/operation/groundintercept.py b/game/operation/groundintercept.py new file mode 100644 index 00000000..3750cad7 --- /dev/null +++ b/game/operation/groundintercept.py @@ -0,0 +1,39 @@ +from dcs.terrain import Terrain + +from game import db +from gen.armor import * +from gen.aircraft import * +from gen.aaa import * +from gen.shipgen import * +from gen.settingsgen import * +from gen.awacsgen import * +from gen.visualgen import * +from gen.conflictgen import Conflict + +from .operation import Operation + +class GroundInterceptOperation(Operation): + def setup(self, + target: db.ArmorDict, + strikegroup: db.PlaneDict): + self.strikegroup = strikegroup + self.target = target + + def prepare(self, terrain: Terrain, is_quick: bool): + super(GroundInterceptOperation, self).prepare(terrain, is_quick) + conflict = Conflict.ground_intercept_conflict( + attacker=self.mission.country(self.attacker_name), + defender=self.mission.country(self.defender_name), + heading=self.to_cp.position.heading_between_point(self.from_cp.position), + from_cp=self.from_cp, + to_cp=self.to_cp + ) + + self.initialize(mission=self.mission, + conflict=conflict) + + def generate(self): + self.airgen.generate_cas_strikegroup(self.strikegroup, clients=self.attacker_clients, at=self.attackers_starting_position) + self.armorgen.generate({}, self.target) + + super(GroundInterceptOperation, self).generate() diff --git a/game/operation/intercept.py b/game/operation/intercept.py new file mode 100644 index 00000000..92cb09f3 --- /dev/null +++ b/game/operation/intercept.py @@ -0,0 +1,49 @@ +from dcs.terrain import Terrain + +from gen import * +from .operation import Operation + + +class InterceptOperation(Operation): + escort = None # type: db.PlaneDict + transport = None # type: db.PlaneDict + interceptors = None # type: db.PlaneDict + airdefense = None # type: db.AirDefenseDict + + def setup(self, + escort: db.PlaneDict, + transport: db.PlaneDict, + airdefense: db.AirDefenseDict, + interceptors: db.PlaneDict): + 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: + self.attackers_starting_position = None + + conflict = Conflict.intercept_conflict( + attacker=self.mission.country(self.attacker_name), + defender=self.mission.country(self.defender_name), + from_cp=self.from_cp, + to_cp=self.to_cp + ) + + self.initialize(mission=self.mission, + conflict=conflict) + + def generate(self): + self.airgen.generate_transport(self.transport, self.to_cp.at) + self.airgen.generate_transport_escort(self.escort, clients=self.defender_clients) + + if self.from_cp.is_global: + super(InterceptOperation, self).generate() + self.airgen.generate_interception(self.interceptors, clients=self.attacker_clients, at=self.attackers_starting_position) + else: + self.airgen.generate_interception(self.interceptors, clients=self.attacker_clients, at=self.attackers_starting_position) + super(InterceptOperation, self).generate() + diff --git a/game/operation/navalintercept.py b/game/operation/navalintercept.py new file mode 100644 index 00000000..76db55fd --- /dev/null +++ b/game/operation/navalintercept.py @@ -0,0 +1,49 @@ +from dcs.terrain import Terrain + +from gen import * +from .operation import Operation + + +class NavalInterceptionOperation(Operation): + strikegroup = None # type: db.PlaneDict + interceptors = None # type: db.PlaneDict + targets = None # type: db.ShipDict + + def setup(self, + strikegroup: db.PlaneDict, + interceptors: db.PlaneDict, + targets: db.ShipDict): + self.strikegroup = strikegroup + self.interceptors = interceptors + self.targets = targets + + def prepare(self, terrain: Terrain, is_quick: bool): + super(NavalInterceptionOperation, self).prepare(terrain, is_quick) + + conflict = Conflict.naval_intercept_conflict( + attacker=self.mission.country(self.attacker_name), + defender=self.mission.country(self.defender_name), + theater=self.game.theater, + from_cp=self.from_cp, + to_cp=self.to_cp + ) + + self.initialize(self.mission, conflict) + + def generate(self): + super(NavalInterceptionOperation, self).generate() + + self.airgen.generate_ship_strikegroup( + attackers= self.strikegroup, + clients=self.attacker_clients, + at=self.attackers_starting_position + ) + + self.airgen.generate_interception( + interceptors=self.interceptors, + clients=self.defender_clients, + at=self.defenders_starting_position + ) + + self.shipgen.generate_cargo(units=self.targets) + diff --git a/game/operation/operation.py b/game/operation/operation.py new file mode 100644 index 00000000..088b81d5 --- /dev/null +++ b/game/operation/operation.py @@ -0,0 +1,93 @@ +from dcs.terrain import Terrain + +from userdata.debriefing import * + +from theater import * +from gen import * + + +class Operation: + attackers_starting_position = None # type: db.StartingPosition + defenders_starting_position = None # type: db.StartingPosition + mission = None # type: dcs.Mission + conflict = None # type: Conflict + armorgen = None # type: ArmorConflictGenerator + airgen = None # type: AircraftConflictGenerator + aagen = None # type: AAConflictGenerator + extra_aagen = None # type: ExtraAAConflictGenerator + shipgen = None # type: ShipGenerator + envgen = None # type: SettingsGenerator + awacsgen = None # type: AWACSConflictGenerator + visualgen = None # type: VisualGenerator + + is_awacs_enabled = False + + def __init__(self, + game, + attacker_name: str, + defender_name: str, + attacker_clients: db.PlaneDict, + defender_clients: db.PlaneDict, + from_cp: ControlPoint, + to_cp: ControlPoint = None): + self.game = game + self.attacker_name = attacker_name + self.defender_name = defender_name + self.attacker_clients = attacker_clients + self.defender_clients = defender_clients + self.from_cp = from_cp + self.to_cp = to_cp + self.is_quick = False + + def initialize(self, mission: Mission, conflict: Conflict): + self.mission = mission + self.conflict = conflict + + self.armorgen = ArmorConflictGenerator(mission, conflict) + self.airgen = AircraftConflictGenerator(mission, conflict) + self.aagen = AAConflictGenerator(mission, conflict) + self.shipgen = ShipGenerator(mission, conflict) + self.awacsgen = AWACSConflictGenerator(mission, conflict, self.game) + self.envgen = SettingsGenerator(mission, conflict, self.game) + self.visualgen = VisualGenerator(mission, conflict, self.game) + + player_name = self.from_cp.captured and self.attacker_name or self.defender_name + enemy_name = self.from_cp.captured and self.defender_name or self.attacker_name + self.extra_aagen = ExtraAAConflictGenerator(mission, conflict, self.game, player_name, enemy_name) + + def prepare(self, terrain: Terrain, is_quick: bool): + self.mission = dcs.Mission(terrain) + self.is_quick = is_quick + + if is_quick: + self.attackers_starting_position = None + self.defenders_starting_position = None + else: + self.attackers_starting_position = self.from_cp.at + self.defenders_starting_position = self.to_cp.at + + def generate(self): + self.visualgen.generate() + + if self.is_awacs_enabled: + self.awacsgen.generate() + + self.extra_aagen.generate() + self.envgen.generate(self.is_quick) + + for global_cp in self.game.theater.controlpoints: + if not global_cp.is_global: + continue + + ship = self.shipgen.generate_carrier(type=db.find_unittype(Carriage, self.game.player)[0], + country=self.game.player, + at=global_cp.at) + + if global_cp == self.from_cp and not self.is_quick: + self.attackers_starting_position = ship + + def units_of(self, country_name: str) -> typing.Collection[UnitType]: + return [] + + def is_successfull(self, debriefing: Debriefing) -> bool: + return True diff --git a/gen/__init__.py b/gen/__init__.py index 858820db..5a0ac6a4 100644 --- a/gen/__init__.py +++ b/gen/__init__.py @@ -1,2 +1,11 @@ -import dcs +from .aaa import * +from .aircraft import * +from .armor import * +from .awacsgen import * +from .conflictgen import * +from .shipgen import * +from .visualgen import * +from .settingsgen import * + +from . import naming diff --git a/gen/aaa.py b/gen/aaa.py index a6cee05f..b1354cae 100644 --- a/gen/aaa.py +++ b/gen/aaa.py @@ -1,4 +1,4 @@ -from game import db +from game import * from theater.conflicttheater import ConflictTheater from .conflictgen import * diff --git a/gen/aircraft.py b/gen/aircraft.py index e99ddd73..1896190f 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -176,7 +176,7 @@ class AircraftConflictGenerator: groups.append(group) return groups - def generate_cas(self, attackers: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None): + def generate_cas_strikegroup(self, attackers: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None): assert len(self.escort_targets) == 0 for flying_type, count, client_count in self._split_to_groups(attackers, clients): @@ -195,7 +195,26 @@ class AircraftConflictGenerator: group.add_waypoint(self.conflict.from_cp.position, RTB_ALTITUDE) - def generate_cas_escort(self, attackers: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None): + def generate_ship_strikegroup(self, attackers: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None): + assert len(self.escort_targets) == 0 + + for flying_type, count, client_count in self._split_to_groups(attackers, clients): + group = self._generate_group( + name=namegen.next_cas_group_name(), + side=self.conflict.attackers_side, + unit_type=flying_type, + count=count, + client_count=client_count, + at=at and at or self._group_point(self.conflict.air_attackers_location)) + self.escort_targets.append(group) + + group.add_waypoint(self.conflict.position, CAS_ALTITUDE, WARM_START_AIRSPEED) + group.task = AntishipStrike.name + self._setup_group(group, AntishipStrike, clients) + + group.add_waypoint(self.conflict.from_cp.position, RTB_ALTITUDE) + + def generate_strikegroup_escort(self, attackers: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None): for g in self._generate_escort( side=self.conflict.attackers_side, units=attackers, diff --git a/gen/conflictgen.py b/gen/conflictgen.py index 25a9e574..68a26c9f 100644 --- a/gen/conflictgen.py +++ b/gen/conflictgen.py @@ -14,6 +14,9 @@ from dcs.point import * from dcs.task import * from dcs.country import * +from theater import * + + GROUND_DISTANCE_FACTOR = 0.8 GROUNDINTERCEPT_DISTANCE_FACTOR = 3 AIR_DISTANCE = 32000 @@ -25,6 +28,9 @@ INTERCEPT_DEFENDERS_DISTANCE = 30000 INTERCEPT_MAX_DISTANCE = 80000 INTERCEPT_MIN_DISTANCE = 45000 +NAVAL_INTERCEPT_DISTANCE_FACTOR = 1.3 +NAVAL_INTERCEPT_STEP = 3000 + def _opposite_heading(h): return h+180 @@ -119,3 +125,31 @@ class Conflict: instance.ground_defenders_location = instance.position.point_from_heading(random.choice(to_cp.radials), instance.size * GROUNDINTERCEPT_DISTANCE_FACTOR) return instance + + @classmethod + def naval_intercept_conflict(cls, attacker: Country, defender: Country, theater: ConflictTheater, from_cp: ControlPoint, to_cp: ControlPoint): + radial = random.choice(to_cp.sea_radials) + + initial_distance = int(from_cp.position.distance_to_point(to_cp.position) * NAVAL_INTERCEPT_DISTANCE_FACTOR) + position = to_cp.position.point_from_heading(radial, initial_distance) + for offset in range(0, initial_distance, NAVAL_INTERCEPT_STEP): + if theater.is_on_land(position): + break + else: + position = to_cp.position.point_from_heading(radial, offset) + + instance = cls() + instance.from_cp = from_cp + instance.to_cp = to_cp + instance.attackers_side = attacker + instance.defenders_side = defender + + instance.position = position + instance.size = SIZE_REGULAR + instance.radials = to_cp.radials + + attacker_heading = from_cp.position.heading_between_point(to_cp.position) + instance.air_attackers_location = instance.position.point_from_heading(attacker_heading, AIR_DISTANCE) + instance.air_defenders_location = instance.position.point_from_heading(_opposite_heading(attacker_heading), AIR_DISTANCE) + + return instance diff --git a/gen/settingsgen.py b/gen/settingsgen.py index 60337dc4..e2f71637 100644 --- a/gen/settingsgen.py +++ b/gen/settingsgen.py @@ -9,8 +9,8 @@ from dcs.action import * from dcs.unit import Skill from game import db -from theater.weatherforecast import WeatherForecast -from theater.conflicttheater import Conflict +from theater import * +from gen import * ACTIVATION_TRIGGER_SIZE = 40000 ACTIVATION_TRIGGER_MIN_DISTANCE = 5000 diff --git a/gen/shipgen.py b/gen/shipgen.py index ea6c95f7..93b23207 100644 --- a/gen/shipgen.py +++ b/gen/shipgen.py @@ -1,3 +1,4 @@ +from game import db from .conflictgen import * from .naming import * @@ -5,15 +6,33 @@ from dcs.mission import * from dcs.unitgroup import * from dcs.task import * +SHIP_RANDOM_SPREAD = 300 + class ShipGenerator: def __init__(self, mission: Mission, conflict: Conflict): self.m = mission self.conflict = conflict - def generate(self, type: ShipType, country: str, at: Point) -> ShipGroup: + def generate_carrier(self, type: ShipType, country: str, at: Point) -> ShipGroup: return self.m.ship_group( country=self.m.country(country), name=namegen.next_transport_group_name(), _type=type, position=at) + + def generate_cargo(self, units: db.ShipDict) -> typing.Collection[ShipGroup]: + groups = [] + for unit_type, unit_count in units.items(): + group = self.m.ship_group( + country=self.conflict.defenders_side, + name=namegen.next_transport_group_name(), + _type=unit_type, + position=self.conflict.position.random_point_within(SHIP_RANDOM_SPREAD, SHIP_RANDOM_SPREAD), + group_size=unit_count, + ) + + group.add_waypoint(self.conflict.to_cp.position) + groups.append(group) + + return groups diff --git a/gen/visualgen.py b/gen/visualgen.py index 49eb39ec..e0d38daf 100644 --- a/gen/visualgen.py +++ b/gen/visualgen.py @@ -6,7 +6,8 @@ from dcs.mission import Mission from dcs.statics import * from dcs.unit import Static -from theater.conflicttheater import Conflict +from theater import * +from .conflictgen import * #from game.game import Game diff --git a/theater/__init__.py b/theater/__init__.py index e69de29b..7e1236e3 100644 --- a/theater/__init__.py +++ b/theater/__init__.py @@ -0,0 +1,3 @@ +from .controlpoint import * +from .conflicttheater import * +from .base import * \ No newline at end of file diff --git a/theater/base.py b/theater/base.py index fdb4971f..3fdf068a 100644 --- a/theater/base.py +++ b/theater/base.py @@ -88,13 +88,6 @@ class Base: total_scrambled += PLANES_IN_GROUP yield total_scrambled < total_planes and PLANES_IN_GROUP or total_planes - total_scrambled - def _group_sizes_for(self, target: ControlPoint) -> typing.List[int]: - total_planes = target.importance * PLANES_IMPORTANCE_FACTOR - total_scrambled = 0 - for _ in range(math.ceil(total_planes / PLANES_IN_GROUP)): - total_scrambled += PLANES_IN_GROUP - yield PLANES_IN_GROUP and total_scrambled < total_planes or total_planes - total_scrambled - def append_commision_points(self, for_type, points: float) -> int: self.commision_points[for_type] = self.commision_points.get(for_type, 0) + points points = self.commision_points[for_type] diff --git a/theater/conflicttheater.py b/theater/conflicttheater.py index b2242d4f..3aea52ca 100644 --- a/theater/conflicttheater.py +++ b/theater/conflicttheater.py @@ -2,9 +2,10 @@ import typing import itertools import dcs +from dcs.mapping import Point from .landmap import ray_tracing -from .controlpoint import * +from .controlpoint import ControlPoint SIZE_TINY = 150 SIZE_SMALL = 600 diff --git a/theater/controlpoint.py b/theater/controlpoint.py index 1acdf73f..48689805 100644 --- a/theater/controlpoint.py +++ b/theater/controlpoint.py @@ -5,8 +5,6 @@ from dcs.mapping import * from dcs.country import * from dcs.terrain import Airport -from gen.conflictgen import Conflict - class ControlPoint: connected_points = [] # type: typing.List[ControlPoint] @@ -47,6 +45,16 @@ class ControlPoint: def is_global(self): return not self.connected_points + @property + def sea_radials(self) -> typing.Collection[int]: + # TODO: fix imports + all_radials = [0, 45, 90, 135, 180, 225, 270, 315, ] + result = [] + for r in all_radials: + if r not in self.radials: + result.append(r) + return result + def connect(self, to): self.connected_points.append(to) @@ -64,15 +72,3 @@ class ControlPoint: return closest_radial - def conflict_attack(self, from_cp, attacker: Country, defender: Country) -> Conflict: - attack_radial = self.find_radial(self.position.heading_between_point(from_cp.position)) - defense_radial = self.find_radial(from_cp.position.heading_between_point(self.position), ignored_radial=attack_radial) - - pos = self.position.point_from_heading(0, 1000) - return Conflict.capture_conflict(attacker=attacker, - attack_heading=attack_radial, - defender=defender, - defense_heading=defense_radial, - position=pos, - size=self.size, - radials=self.radials) diff --git a/theater/landmap.py b/theater/landmap.py index 8c413162..ce99252e 100644 --- a/theater/landmap.py +++ b/theater/landmap.py @@ -2,8 +2,11 @@ import pickle def load_poly(filename: str): - with open(filename, "rb") as f: - return pickle.load(f) + try: + with open(filename, "rb") as f: + return pickle.load(f) + except: + return None def ray_tracing(x, y, poly): diff --git a/ui/eventmenu.py b/ui/eventmenu.py index 69e54975..7b45bd38 100644 --- a/ui/eventmenu.py +++ b/ui/eventmenu.py @@ -1,7 +1,7 @@ from ui.eventresultsmenu import * -from game.game import * -from game import event, db +from game import * +from game.event import * class EventMenu(Menu): @@ -40,33 +40,37 @@ class EventMenu(Menu): def scrable_row(unit_type, unit_count): nonlocal row Label(self.frame, text="{} ({})".format(db.unit_type_name(unit_type), unit_count)).grid(row=row, sticky=W) - scramble_entry = Entry(self.frame, width=10) - scramble_entry.grid(column=1, row=row) + + scramble_entry = Entry(self.frame, width=2) + scramble_entry.grid(column=1, row=row, sticky=W) scramble_entry.insert(0, "0") self.aircraft_scramble_entries[unit_type] = scramble_entry + Button(self.frame, text="+", command=self.scramble_half(True, unit_type)).grid(column=2, row=row) - client_entry = Entry(self.frame, width=10) - client_entry.grid(column=2, row=row) + client_entry = Entry(self.frame, width=2) + client_entry.grid(column=3, row=row, sticky=E) client_entry.insert(0, "0") self.aircraft_client_entries[unit_type] = client_entry + Button(self.frame, text="+", command=self.client_one(unit_type)).grid(column=4, row=row) row += 1 def scramble_armor_row(unit_type, unit_count): nonlocal row Label(self.frame, text="{} ({})".format(db.unit_type_name(unit_type), unit_count)).grid(row=row, sticky=W) - scramble_entry = Entry(self.frame, width=10) + scramble_entry = Entry(self.frame, width=2) scramble_entry.insert(0, "0") scramble_entry.grid(column=1, row=row) self.armor_scramble_entries[unit_type] = scramble_entry + Button(self.frame, text="+", command=self.scramble_half(False, unit_type)).grid(column=2, row=row) row += 1 - Label(self.frame, text="{}. {}".format(self.event, self.event.threat_description)).grid(row=row, column=0, columnspan=3) + Label(self.frame, text="{}. {}".format(self.event, self.event.threat_description)).grid(row=row, column=0, columnspan=5) row += 1 - Button(self.frame, text="Commit", command=self.start).grid(column=1, row=row, sticky=E) - Button(self.frame, text="Back", command=self.dismiss).grid(column=2, row=row, sticky=E) + Button(self.frame, text="Commit", command=self.start).grid(column=3, row=row, sticky=E) + Button(self.frame, text="Back", command=self.dismiss).grid(column=4, row=row, sticky=E) awacs_enabled = self.game.budget >= AWACS_BUDGET_COST and NORMAL or DISABLED Checkbutton(self.frame, text="AWACS ({}m)".format(AWACS_BUDGET_COST), var=self.awacs, state=awacs_enabled).grid(row=row, column=0, sticky=W) @@ -75,8 +79,8 @@ class EventMenu(Menu): label("Aircraft") if self.base.aircraft: - label("Amount", row, 1) - label("Client slots", row, 2) + Label(self.frame, text="Amount").grid(row=row, column=1, columnspan=2) + Label(self.frame, text="Client slots").grid(row=row, column=3, columnspan=2) row += 1 for unit_type, count in self.base.aircraft.items(): @@ -92,6 +96,43 @@ class EventMenu(Menu): if not self.base.total_armor: label("None", sticky=W) + def _scrambled_aircraft_count(self, unit_type: UnitType) -> int: + value = self.aircraft_scramble_entries[unit_type].get() + if value and int(value) > 0: + return min(int(value), self.base.aircraft[unit_type]) + return 0 + + def _scrambled_armor_count(self, unit_type: UnitType) -> int: + value = self.armor_scramble_entries[unit_type].get() + if value and int(value) > 0: + return min(int(value), self.base.armor[unit_type]) + return 0 + + def scramble_half(self, aircraft: bool, unit_type: UnitType) -> typing.Callable: + def action(): + entry = None # type: Entry + total_count = 0 + if aircraft: + entry = self.aircraft_scramble_entries[unit_type] + total_count = self.base.aircraft[unit_type] + else: + entry = self.armor_scramble_entries[unit_type] + total_count = self.base.armor[unit_type] + + existing_count = int(entry.get()) + entry.delete(0, END) + entry.insert(0, "{}".format(int(existing_count + math.ceil(total_count/2)))) + + return action + + def client_one(self, unit_type: UnitType) -> typing.Callable: + def action(): + entry = self.aircraft_client_entries[unit_type] # type: Entry + amount = int(entry.get()) + entry.delete(0, END) + entry.insert(0, str(amount+1)) + return action + def start(self): if self.awacs.get() == 1: self.event.is_awacs_enabled = True @@ -103,9 +144,8 @@ class EventMenu(Menu): scrambled_sweep = {} scrambled_cas = {} for unit_type, field in self.aircraft_scramble_entries.items(): - value = field.get() - if value and int(value) > 0: - amount = min(int(value), self.base.aircraft[unit_type]) + amount = self._scrambled_aircraft_count(unit_type) + if amount > 0: task = db.unit_task(unit_type) scrambled_aircraft[unit_type] = amount @@ -123,9 +163,9 @@ class EventMenu(Menu): scrambled_armor = {} for unit_type, field in self.armor_scramble_entries.items(): - value = field.get() - if value and int(value) > 0: - scrambled_armor[unit_type] = int(value) + amount = self._scrambled_armor_count(unit_type) + if amount > 0: + scrambled_armor[unit_type] = amount if type(self.event) is CaptureEvent: e = self.event # type: CaptureEvent @@ -148,6 +188,9 @@ class EventMenu(Menu): elif type(self.event) is GroundInterceptEvent: e = self.event # type: GroundInterceptEvent e.player_attacking(strikegroup=scrambled_aircraft, clients=scrambled_clients) + elif type(self.event) is NavalInterceptEvent: + e = self.event # type: NavalInterceptEvent + e.player_attacking(strikegroup=scrambled_aircraft, clients=scrambled_clients) self.game.initiate_event(self.event) EventResultsMenu(self.window, self.parent, self.game, self.event).display() diff --git a/ui/eventresultsmenu.py b/ui/eventresultsmenu.py index a19b0c81..caad0b26 100644 --- a/ui/eventresultsmenu.py +++ b/ui/eventresultsmenu.py @@ -2,6 +2,7 @@ from tkinter.ttk import * from ui.window import * from game.game import * +from userdata.debriefing import * class EventResultsMenu(Menu): diff --git a/ui/overviewcanvas.py b/ui/overviewcanvas.py index efef9c59..6eeb5747 100644 --- a/ui/overviewcanvas.py +++ b/ui/overviewcanvas.py @@ -1,3 +1,5 @@ +import os + from tkinter import * from tkinter.ttk import *