From 07128bb5e6f416001401aedc723c7fba933c07e1 Mon Sep 17 00:00:00 2001 From: Vasiliy Horbachenko Date: Tue, 17 Jul 2018 05:53:55 +0300 Subject: [PATCH 1/5] fixed capture issue when two or more points were attacking the base --- game/event/capture.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/game/event/capture.py b/game/event/capture.py index 2d44a5d5..304895c4 100644 --- a/game/event/capture.py +++ b/game/event/capture.py @@ -41,7 +41,7 @@ class CaptureEvent(Event): self.to_cp.base.affect_strength(+self.STRENGTH_RECOVERY) def skip(self): - if self.to_cp.captured: + if not self.is_player_attacking and self.to_cp.captured: self.to_cp.captured = False def player_defending(self, interceptors: db.PlaneDict, clients: db.PlaneDict): From 6f5835a2b82e32811f83f43ac3d20506e3f685be Mon Sep 17 00:00:00 2001 From: Vasyl Horbachenko Date: Mon, 16 Jul 2018 21:37:35 +0300 Subject: [PATCH 2/5] Improved Frontline CAS --- game/db.py | 16 +++ game/event/__init__.py | 2 +- game/event/frontlinecas.py | 91 +++++++++++++++ game/event/groundattack.py | 4 +- game/event/groundintercept.py | 106 ------------------ game/game.py | 7 +- .../{groundintercept.py => frontlinecas.py} | 27 ++--- gen/aircraft.py | 7 ++ gen/armor.py | 59 ++++++++-- gen/conflictgen.py | 103 ++++++++++++++--- gen/visualgen.py | 3 +- theater/controlpoint.py | 12 +- theater/persiangulf.py | 10 +- ui/eventmenu.py | 4 +- 14 files changed, 283 insertions(+), 168 deletions(-) create mode 100644 game/event/frontlinecas.py delete mode 100644 game/event/groundintercept.py rename game/operation/{groundintercept.py => frontlinecas.py} (66%) diff --git a/game/db.py b/game/db.py index 47cc07da..2a2e44c5 100644 --- a/game/db.py +++ b/game/db.py @@ -432,6 +432,22 @@ def choose_units(for_task: Task, factor: float, count: int, country: str) -> typ return list(set(suitable_unittypes[index_start:index_end])) +def unitdict_append(unit_dict: UnitsDict, unit_type: UnitType, count: int): + unit_dict[unit_type] = unit_dict.get(unit_type, 0) + 1 + + +def unitdict_split(unit_dict: UnitsDict, count: int): + buffer_dict = {} + for unit_type, unit_count in unit_dict.items(): + for _ in range(unit_count): + unitdict_append(buffer_dict, unit_type, 1) + if sum(buffer_dict.values()) >= count: + yield buffer_dict + buffer_dict = {} + + if len(buffer_dict): + yield buffer_dict + def _validate_db(): # check unit by task uniquity total_set = set() diff --git a/game/event/__init__.py b/game/event/__init__.py index ef0eb8d9..f43dc91a 100644 --- a/game/event/__init__.py +++ b/game/event/__init__.py @@ -1,5 +1,5 @@ from .event import * -from .groundintercept import * +from .frontlinecas import * from .intercept import * from .capture import * from .navalintercept import * diff --git a/game/event/frontlinecas.py b/game/event/frontlinecas.py new file mode 100644 index 00000000..07c3e610 --- /dev/null +++ b/game/event/frontlinecas.py @@ -0,0 +1,91 @@ +import math +import random + +from dcs.task import * +from dcs.vehicles import AirDefence + +from game import * +from game.event import * +from game.operation.frontlinecas import FrontlineCASOperation +from userdata.debriefing import Debriefing + + +class FrontlineCASEvent(Event): + TARGET_VARIETY = 2 + TARGET_AMOUNT_FACTOR = 0.5 + ATTACKER_AMOUNT_FACTOR = 0.4 + STRENGTH_INFLUENCE = 0.3 + SUCCESS_MIN_TARGETS = 3 + + targets = None # type: db.ArmorDict + + @property + def threat_description(self): + if not self.game.is_player_attack(self): + return "{} aicraft".format(self.from_cp.base.scramble_count(self.game.settings.multiplier, CAS)) + else: + return super(FrontlineCASEvent, self).threat_description + + def __str__(self): + return "Frontline CAS 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 + + if self.from_cp.captured: + return float(destroyed_targets) >= min(self.SUCCESS_MIN_TARGETS, total_targets) + else: + return float(destroyed_targets) < min(self.SUCCESS_MIN_TARGETS, total_targets) + + def commit(self, debriefing: Debriefing): + super(FrontlineCASEvent, 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): + if self.to_cp.captured: + self.to_cp.base.affect_strength(-0.1) + + def player_attacking(self, strikegroup: db.PlaneDict, clients: db.PlaneDict): + suitable_armor_targets = db.find_unittype(PinpointStrike, self.defender_name) + random.shuffle(suitable_armor_targets) + + target_types = suitable_armor_targets[:self.TARGET_VARIETY] + typecount = max(math.floor(self.to_cp.base.assemble_count() * self.TARGET_AMOUNT_FACTOR), 1) + self.targets = {unittype: typecount for unittype in target_types} + + defense_aa_unit = random.choice(self.game.commision_unit_types(self.to_cp, AirDefence)) + self.targets[defense_aa_unit] = 1 + + suitable_armor_attackers = db.find_unittype(PinpointStrike, self.attacker_name) + random.shuffle(suitable_armor_attackers) + attacker_types = suitable_armor_attackers[:self.TARGET_VARIETY] + typecount = max(math.floor(self.from_cp.base.assemble_count() * self.ATTACKER_AMOUNT_FACTOR), 1) + attackers = {unittype: typecount for unittype in attacker_types} + + op = FrontlineCASOperation(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, + attackers=attackers, + strikegroup=strikegroup) + + self.operation = op + diff --git a/game/event/groundattack.py b/game/event/groundattack.py index ad55d4d5..9b32523f 100644 --- a/game/event/groundattack.py +++ b/game/event/groundattack.py @@ -5,11 +5,11 @@ from dcs.task import * from game import * from game.event import * -from game.event.groundintercept import GroundInterceptEvent +from game.event.frontlinecas import FrontlineCASEvent from game.operation.groundattack import GroundAttackOperation -class GroundAttackEvent(GroundInterceptEvent): +class GroundAttackEvent(FrontlineCASEvent): def __str__(self): return "Destroy insurgents at {}".format(self.to_cp) diff --git a/game/event/groundintercept.py b/game/event/groundintercept.py deleted file mode 100644 index 1cac6288..00000000 --- a/game/event/groundintercept.py +++ /dev/null @@ -1,106 +0,0 @@ -import math -import random - -from dcs.task import * -from dcs.vehicles import AirDefence - -from game import * -from game.event import * -from game.operation.groundintercept import GroundInterceptOperation -from userdata.debriefing import Debriefing - - -class GroundInterceptEvent(Event): - TARGET_AMOUNT_FACTOR = 2 - TARGET_VARIETY = 2 - STRENGTH_INFLUENCE = 0.3 - SUCCESS_TARGETS_HIT_PERCENTAGE = 0.5 - - targets = None # type: db.ArmorDict - - @property - def threat_description(self): - if not self.game.is_player_attack(self): - return "{} aicraft".format(self.from_cp.base.scramble_count(self.game.settings.multiplier, CAS)) - else: - return super(GroundInterceptEvent, self).threat_description - - def __str__(self): - return "Frontline CAS 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 - - if self.from_cp.captured: - return float(destroyed_targets) / total_targets >= self.SUCCESS_TARGETS_HIT_PERCENTAGE - else: - return float(destroyed_targets) / 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: - 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(-0.1) - - 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} - - defense_aa_unit = random.choice(self.game.commision_unit_types(self.to_cp, AirDefence)) - self.targets[defense_aa_unit] = 1 - - 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, - interceptors={}) - - self.operation = op - - def player_defending(self, interceptors: 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( - 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 - ) - - strikegroup = self.from_cp.base.scramble_cas(self.game.settings.multiplier) - op.setup(target=self.targets, - strikegroup=strikegroup, - interceptors=interceptors) - - self.operation = op diff --git a/game/game.py b/game/game.py index b0e51337..1964b4e4 100644 --- a/game/game.py +++ b/game/game.py @@ -39,7 +39,7 @@ For the enemy events, only 1 event of each type could be generated for a turn. Events: * CaptureEvent - capture base * InterceptEvent - air intercept -* GroundInterceptEvent - frontline CAS +* FrontlineCASEvent - frontline CAS * GroundAttackEvent - destroy insurgents * NavalInterceptEvent - naval intercept * AntiAAStrikeEvent - anti-AA strike @@ -48,7 +48,7 @@ Events: EVENT_PROBABILITIES = { CaptureEvent: [100, 10], InterceptEvent: [25, 10], - GroundInterceptEvent: [25, 10], + FrontlineCASEvent: [250, 0], GroundAttackEvent: [0, 10], NavalInterceptEvent: [25, 10], AntiAAStrikeEvent: [25, 10], @@ -212,7 +212,8 @@ class Game: def pass_turn(self, no_action=False, ignored_cps: typing.Collection[ControlPoint]=None): for event in self.events: - event.skip() + if isinstance(event, UnitsDeliveryEvent): + event.skip() if not no_action: self._budget_player() diff --git a/game/operation/groundintercept.py b/game/operation/frontlinecas.py similarity index 66% rename from game/operation/groundintercept.py rename to game/operation/frontlinecas.py index 715b1b9e..3f2556b4 100644 --- a/game/operation/groundintercept.py +++ b/game/operation/frontlinecas.py @@ -1,3 +1,5 @@ +from itertools import zip_longest + from dcs.terrain import Terrain from game import db @@ -13,26 +15,29 @@ from gen.conflictgen import Conflict from .operation import Operation -class GroundInterceptOperation(Operation): +MAX_DISTANCE_BETWEEN_GROUPS = 12000 + + +class FrontlineCASOperation(Operation): + attackers = None # type: db.ArmorDict strikegroup = None # type: db.PlaneDict - interceptors = None # type: db.PlaneDict target = None # type: db.ArmorDict def setup(self, target: db.ArmorDict, - strikegroup: db.PlaneDict, - interceptors: db.PlaneDict): + attackers: db.ArmorDict, + strikegroup: db.PlaneDict): self.strikegroup = strikegroup - self.interceptors = interceptors self.target = target + self.attackers = attackers def prepare(self, terrain: Terrain, is_quick: bool): - super(GroundInterceptOperation, self).prepare(terrain, is_quick) + super(FrontlineCASOperation, self).prepare(terrain, is_quick) if self.defender_name == self.game.player: self.attackers_starting_position = None self.defenders_starting_position = None - conflict = Conflict.ground_intercept_conflict( + conflict = Conflict.frontline_cas_conflict( attacker=self.mission.country(self.attacker_name), defender=self.mission.country(self.defender_name), from_cp=self.from_cp, @@ -44,10 +49,6 @@ class GroundInterceptOperation(Operation): conflict=conflict) def generate(self): + self.armorgen.generate_vec(self.attackers, self.target) self.airgen.generate_cas_strikegroup(self.strikegroup, clients=self.attacker_clients, at=self.attackers_starting_position) - - if self.interceptors: - self.airgen.generate_defense(self.interceptors, clients=self.defender_clients, at=self.defenders_starting_position) - - self.armorgen.generate({}, self.target) - super(GroundInterceptOperation, self).generate() + super(FrontlineCASOperation, self).generate() diff --git a/gen/aircraft.py b/gen/aircraft.py index b1359219..48ee4c8c 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -242,6 +242,9 @@ class AircraftConflictGenerator: at=at and at or self._group_point(self.conflict.air_attackers_location)) waypoint = group.add_waypoint(self.conflict.position, CAS_ALTITUDE, WARM_START_AIRSPEED) + if self.conflict.is_vector: + group.add_waypoint(self.conflict.tail, CAS_ALTITUDE, WARM_START_ALTITUDE) + group.task = CAS.name self._setup_group(group, CAS, client_count) self.escort_targets.append((group, group.points.index(waypoint))) @@ -341,6 +344,10 @@ class AircraftConflictGenerator: wayp = group.add_waypoint(self.conflict.position, WARM_START_ALTITUDE, INTERCEPTION_AIRSPEED) wayp.tasks.append(EngageTargets(max_distance=INTERCEPT_MAX_DISTANCE)) + + if self.conflict.is_vector: + group.add_waypoint(self.conflict.tail, CAS_ALTITUDE, WARM_START_ALTITUDE) + self._setup_group(group, CAP, client_count) self._rtb_for(group, self.conflict.from_cp, at) diff --git a/gen/armor.py b/gen/armor.py index 2a465fec..80895b04 100644 --- a/gen/armor.py +++ b/gen/armor.py @@ -1,3 +1,5 @@ +from itertools import zip_longest + from game import db from .conflictgen import * from .naming import * @@ -25,7 +27,7 @@ class ArmorConflictGenerator: 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): + def _generate_group(self, side: Country, unit: VehicleType, count: int, at: Point, to: Point = None): for c in range(count): group = self.m.vehicle_group( side, @@ -34,24 +36,59 @@ class ArmorConflictGenerator: position=self._group_point(at), group_size=1, move_formation=PointAction.OffRoad) - initial_position = self.conflict.position.point_from_heading(0, 500) - wayp = group.add_waypoint(self._group_point(initial_position)) + + if not to: + to = self.conflict.position.point_from_heading(0, 500) + + wayp = group.add_waypoint(self._group_point(to)) wayp.tasks = [] + def _generate_fight_at(self, attackers: db.ArmorDict, defenders: db.ArmorDict, position: Point): + if attackers: + for type, count in attackers.items(): + self._generate_group( + side=self.conflict.attackers_side, + unit=type, + count=count, + at=position.point_from_heading(self.conflict.heading - 90, 600), + to=position) + + if defenders: + for type, count in defenders.items(): + self._generate_group( + side=self.conflict.defenders_side, + unit=type, + count=count, + at=position.point_from_heading(self.conflict.heading + 90, 600), + to=position) + def generate(self, attackers: db.ArmorDict, defenders: db.ArmorDict): for type, count in attackers.items(): self._generate_group( - side=self.conflict.attackers_side, - unit=type, - count=count, - at=self.conflict.ground_attackers_location) + side=self.conflict.attackers_side, + unit=type, + count=count, + at=self.conflict.ground_attackers_location) for type, count in defenders.items(): self._generate_group( - side=self.conflict.defenders_side, - unit=type, - count=count, - at=self.conflict.ground_defenders_location) + side=self.conflict.defenders_side, + unit=type, + count=count, + at=self.conflict.ground_defenders_location) + + def generate_vec(self, attackers: db.ArmorDict, defenders: db.ArmorDict): + defender_groups = list(db.unitdict_split(defenders, 6)) + distance_between_groups = min(self.conflict.distance / len(defender_groups), 12000) + total_distance = distance_between_groups * len(defender_groups) + + attacker_groups = list(db.unitdict_split(attackers, + int(sum(attackers.values()) / len(defender_groups)))) + + position = self.conflict.center.point_from_heading(self.conflict.opposite_heading, total_distance / 2) + for attacker_group_dict, target_group_dict in zip_longest(attacker_groups, defender_groups): + position = position.point_from_heading(self.conflict.heading, distance_between_groups) + self._generate_fight_at(attacker_group_dict, target_group_dict, position) def generate_passengers(self, count: int): unit_type = random.choice(db.find_unittype(Nothing, self.conflict.attackers_side.name)) diff --git a/gen/conflictgen.py b/gen/conflictgen.py index fefa91ce..299e6e14 100644 --- a/gen/conflictgen.py +++ b/gen/conflictgen.py @@ -65,28 +65,35 @@ class Conflict: size = None # type: int radials = None # type: typing.List[int] + heading = None # type: int + distance = None # type: int + ground_attackers_location = None # type: Point ground_defenders_location = None # type: Point air_attackers_location = None # type: Point air_defenders_location = None # type: Point def __init__(self, - position: Point, theater: ConflictTheater, from_cp: ControlPoint, to_cp: ControlPoint, attackers_side: Country, defenders_side: Country, - ground_attackers_location: Point, - ground_defenders_location: Point, - air_attackers_location: Point, - air_defenders_location: Point): + position: Point, + heading=None, + distance=None, + ground_attackers_location: Point = None, + ground_defenders_location: Point = None, + air_attackers_location: Point = None, + air_defenders_location: Point = None): self.attackers_side = attackers_side self.defenders_side = defenders_side self.from_cp = from_cp self.to_cp = to_cp self.theater = theater self.position = position + self.heading = heading + self.distance = distance self.size = to_cp.size self.radials = to_cp.radials self.ground_attackers_location = ground_attackers_location @@ -94,15 +101,57 @@ class Conflict: self.air_attackers_location = air_attackers_location self.air_defenders_location = air_defenders_location + @property + def center(self) -> Point: + return self.position.point_from_heading(self.heading, self.distance / 2) + + @property + def tail(self) -> Point: + return self.position.point_from_heading(self.heading, self.distance) + + @property + def is_vector(self) -> bool: + return self.heading is not None + + @property + def opposite_heading(self) -> int: + return _heading_sum(self.heading, 180) + @property def to_size(self): return self.to_cp.size * GROUND_DISTANCE_FACTOR @classmethod - def frontline_position(cls, from_cp: ControlPoint, to_cp: ControlPoint): + def has_frontline_between(cls, from_cp: ControlPoint, to_cp: ControlPoint) -> bool: + return from_cp.has_frontline and to_cp.has_frontline + + @classmethod + def frontline_position(cls, from_cp: ControlPoint, to_cp: ControlPoint) -> typing.Tuple[Point, int]: distance = max(from_cp.position.distance_to_point(to_cp.position) * FRONT_SMOKE_DISTANCE_FACTOR * to_cp.base.strength, FRONT_SMOKE_MIN_DISTANCE) heading = to_cp.position.heading_between_point(from_cp.position) - return to_cp.position.point_from_heading(heading, distance) + return to_cp.position.point_from_heading(heading, distance), heading + + @classmethod + def frontline_vector(cls, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater) -> typing.Tuple[Point, int, int]: + center_position, heading = cls.frontline_position(from_cp, to_cp) + + left_position = center_position + for offset in range(0, 80000, 1000): + pos = center_position.point_from_heading(_heading_sum(heading, -90), offset) + if not theater.is_on_land(pos): + break + else: + left_position = pos + + right_position = center_position + for offset in range(0, 80000, 1000): + pos = center_position.point_from_heading(_heading_sum(heading, 90), offset) + if not theater.is_on_land(pos): + break + else: + right_position = pos + + return left_position, _heading_sum(heading, 90), right_position.distance_to_point(left_position) @classmethod def _find_ground_location(cls, initial: Point, max_distance: int, heading: int, theater: ConflictTheater) -> Point: @@ -189,25 +238,44 @@ class Conflict: ) @classmethod - def ground_intercept_conflict(cls, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater): - heading = to_cp.position.heading_between_point(from_cp.position) - initial_location = cls.frontline_position(from_cp, to_cp).random_point_within(GROUND_INTERCEPT_SPREAD) - position = Conflict._find_ground_location(initial_location, GROUND_INTERCEPT_SPREAD, heading, theater) - if not position: - heading = to_cp.find_radial(to_cp.position.heading_between_point(from_cp.position)) - position = to_cp.position.point_from_heading(heading, to_cp.size * GROUND_DISTANCE_FACTOR) + def frontline_cas_conflict(cls, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater): + assert cls.has_frontline_between(from_cp, to_cp) + position, heading, distance = cls.frontline_vector(from_cp, to_cp, theater) return cls( position=position, + heading=heading, + distance=distance, theater=theater, from_cp=from_cp, to_cp=to_cp, attackers_side=attacker, defenders_side=defender, ground_attackers_location=None, - ground_defenders_location=position, + ground_defenders_location=None, air_attackers_location=position.point_from_heading(random.randint(*INTERCEPT_ATTACKERS_HEADING) + heading, AIR_DISTANCE), - air_defenders_location=position.point_from_heading(random.randint(*INTERCEPT_ATTACKERS_HEADING) + _opposite_heading(heading), AIR_DISTANCE) + air_defenders_location=position.point_from_heading(random.randint(*INTERCEPT_ATTACKERS_HEADING) + _opposite_heading(heading), AIR_DISTANCE), + ) + + @classmethod + def frontline_cap_conflict(cls, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater): + assert cls.has_frontline_between(from_cp, to_cp) + position, heading, distance = cls.frontline_vector(from_cp, to_cp, theater) + defenders_distance = random.randint(distance/3, distance) + + return cls( + position=position, + heading=heading, + distance=distance, + theater=theater, + from_cp=from_cp, + to_cp=to_cp, + attackers_side=attacker, + defenders_side=defender, + ground_attackers_location=None, + ground_defenders_location=None, + air_attackers_location=position.point_from_heading(random.randint(*INTERCEPT_ATTACKERS_HEADING) + heading, AIR_DISTANCE), + air_defenders_location=position.point_from_heading(heading, max(AIR_DISTANCE, defenders_distance)), ) @classmethod @@ -261,8 +329,7 @@ class Conflict: @classmethod def transport_conflict(cls, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater): - frontline_position = cls.frontline_position(from_cp, to_cp) - heading = to_cp.position.heading_between_point(from_cp.position) + frontline_position, heading = cls.frontline_position(from_cp, to_cp) initial_dest = frontline_position.point_from_heading(heading, TRANSPORT_FRONTLINE_DIST) dest = cls._find_ground_location(initial_dest, from_cp.position.distance_to_point(to_cp.position) / 3, heading, theater) if not dest: diff --git a/gen/visualgen.py b/gen/visualgen.py index e399d140..a59c9986 100644 --- a/gen/visualgen.py +++ b/gen/visualgen.py @@ -99,8 +99,7 @@ class VisualGenerator: def _generate_frontline_smokes(self): for from_cp, to_cp in self.game.theater.conflicts(): - heading = to_cp.position.heading_between_point(from_cp.position) - point = Conflict.frontline_position(from_cp, to_cp) + point, heading = Conflict.frontline_position(from_cp, to_cp) plane_start = point.point_from_heading(turn_heading(heading, 90), FRONT_SMOKE_LENGTH / 2) for offset in range(0, FRONT_SMOKE_LENGTH, FRONT_SMOKE_SPACING): diff --git a/theater/controlpoint.py b/theater/controlpoint.py index 9d851382..dff1deab 100644 --- a/theater/controlpoint.py +++ b/theater/controlpoint.py @@ -10,10 +10,11 @@ class ControlPoint: connected_points = [] # type: typing.List[ControlPoint] position = None # type: Point captured = False - base: None # type: theater.base.Base - at: None # type: db.StartPosition + has_frontline = True + base = None # type: theater.base.Base + at = None # type: db.StartPosition - def __init__(self, name: str, position: Point, at, radials: typing.Collection[int], size: int, importance: int): + def __init__(self, name: str, position: Point, at, radials: typing.Collection[int], size: int, importance: int, has_frontline=True): import theater.base self.name = " ".join(re.split(r" |-", name)[:2]) @@ -24,14 +25,15 @@ class ControlPoint: self.size = size self.importance = importance self.captured = False + self.has_frontline = has_frontline self.radials = radials self.connected_points = [] self.base = theater.base.Base() @classmethod - def from_airport(cls, airport: Airport, radials: typing.Collection[int], size: int, importance: int): + def from_airport(cls, airport: Airport, radials: typing.Collection[int], size: int, importance: int, has_frontline=True): assert airport - return cls(airport.name, airport.position, airport, radials, size, importance) + return cls(airport.name, airport.position, airport, radials, size, importance, has_frontline) @classmethod def carrier(cls, name: str, at: Point): diff --git a/theater/persiangulf.py b/theater/persiangulf.py index d2c9ee8f..bdac7a97 100644 --- a/theater/persiangulf.py +++ b/theater/persiangulf.py @@ -29,13 +29,13 @@ class PersianGulfTheater(ConflictTheater): fujairah = ControlPoint.from_airport(persiangulf.Fujairah_Intl, COAST_V_W, SIZE_REGULAR, 1.3) khasab = ControlPoint.from_airport(persiangulf.Khasab, LAND, SIZE_SMALL, 1.3) - sirri = ControlPoint.from_airport(persiangulf.Sirri_Island, COAST_DL_W, SIZE_REGULAR, 1.2) - abu_musa = ControlPoint.from_airport(persiangulf.Abu_Musa_Island_Airport, LAND, SIZE_SMALL, 1.0) - tunb_island = ControlPoint.from_airport(persiangulf.Tunb_Island_AFB, [0, 270, 330], SIZE_REGULAR, 1.1) - tunb_kochak = ControlPoint.from_airport(persiangulf.Tunb_Kochak, [135, 180], SIZE_SMALL, IMPORTANCE_HIGH) + sirri = ControlPoint.from_airport(persiangulf.Sirri_Island, COAST_DL_W, SIZE_REGULAR, 1.2, has_frontline=False) + abu_musa = ControlPoint.from_airport(persiangulf.Abu_Musa_Island_Airport, LAND, SIZE_SMALL, 1.0, has_frontline=False) + tunb_island = ControlPoint.from_airport(persiangulf.Tunb_Island_AFB, [0, 270, 330], SIZE_REGULAR, 1.1, has_frontline=False) + tunb_kochak = ControlPoint.from_airport(persiangulf.Tunb_Kochak, [135, 180], SIZE_SMALL, IMPORTANCE_HIGH, has_frontline=False) bandar_lengeh = ControlPoint.from_airport(persiangulf.Bandar_Lengeh, [270, 315, 0, 45], SIZE_SMALL, IMPORTANCE_HIGH) - qeshm = ControlPoint.from_airport(persiangulf.Qeshm_Island, [270, 315, 0, 45, 90, 135, 180], SIZE_SMALL, 1.1) + qeshm = ControlPoint.from_airport(persiangulf.Qeshm_Island, [270, 315, 0, 45, 90, 135, 180], SIZE_SMALL, 1.1, has_frontline=False) havadarya = ControlPoint.from_airport(persiangulf.Havadarya, COAST_DL_W, SIZE_REGULAR, IMPORTANCE_HIGH) bandar_abbas = ControlPoint.from_airport(persiangulf.Bandar_Abbas_Intl, LAND, SIZE_BIG, IMPORTANCE_HIGH) diff --git a/ui/eventmenu.py b/ui/eventmenu.py index 6538d9e6..e1b2b81a 100644 --- a/ui/eventmenu.py +++ b/ui/eventmenu.py @@ -190,8 +190,8 @@ class EventMenu(Menu): else: e.player_defending(escort=scrambled_aircraft, clients=scrambled_clients) - elif type(self.event) is GroundInterceptEvent: - e = self.event # type: GroundInterceptEvent + elif type(self.event) is FrontlineCASEvent: + e = self.event # type: FrontlineCASEvent if self.game.is_player_attack(self.event): e.player_attacking(strikegroup=scrambled_aircraft, clients=scrambled_clients) else: From 820820eb925403a4846ace8ce57800691a6af78e Mon Sep 17 00:00:00 2001 From: Vasyl Horbachenko Date: Mon, 16 Jul 2018 23:58:01 +0300 Subject: [PATCH 3/5] frontline attack ops --- __init__.py | 39 -------- game/db.py | 5 + game/event/__init__.py | 6 +- game/event/{capture.py => baseattack.py} | 34 +++---- game/event/frontlineattack.py | 76 ++++++++++++++++ game/event/frontlinecas.py | 91 ------------------- .../{groundattack.py => insurgentattack.py} | 20 ++-- game/game.py | 10 +- game/operation/{capture.py => baseattack.py} | 6 +- .../{frontlinecas.py => frontlineattack.py} | 6 +- .../{groundattack.py => insurgentattack.py} | 6 +- gen/armor.py | 23 +++-- gen/conflictgen.py | 11 ++- gen/visualgen.py | 5 +- resources/tools/miz_diff.py | 39 ++++++++ theater/base.py | 5 +- ui/eventmenu.py | 17 ++-- ui/overviewcanvas.py | 10 +- userdata/persistency.py | 2 +- 19 files changed, 205 insertions(+), 206 deletions(-) rename game/event/{capture.py => baseattack.py} (72%) create mode 100644 game/event/frontlineattack.py delete mode 100644 game/event/frontlinecas.py rename game/event/{groundattack.py => insurgentattack.py} (59%) rename game/operation/{capture.py => baseattack.py} (93%) rename game/operation/{frontlinecas.py => frontlineattack.py} (90%) rename game/operation/{groundattack.py => insurgentattack.py} (87%) create mode 100644 resources/tools/miz_diff.py diff --git a/__init__.py b/__init__.py index 8a5d950f..92d39a9c 100755 --- a/__init__.py +++ b/__init__.py @@ -17,45 +17,6 @@ from theater import start_generator from userdata import persistency -""" -from dcs.lua.parse import * -a = loads(open("build/mission", "r").read()) -b = loads(open("build/mission_workin.lua", "r").read()) - - -def get(a, k): - b = a - for x in k.strip().split(" "): - if isinstance(a, dict): - y = a - a = a.get(x, None) - if a is None: - try: - a = y.get(int(x), None) - except: - pass - else: - break - if a is None: - pass - return a - -def cycle(kk, ref, v): - if isinstance(v, dict): - for k, v in v.items(): - cycle(kk + " " + str(k), ref, v) - elif isinstance(v, list): - for i, v in enumerate(v): - cycle(kk + " " + str(i), ref, v) - else: - if get(ref, kk) != v: - print(kk, v) - print(get(ref, kk)) - -cycle("", a, b) -sys.exit(0) -""" - persistency.setup(sys.argv[1]) dcs.planes.FlyingType.payload_dirs.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "resources\\payloads")) diff --git a/game/db.py b/game/db.py index 2a2e44c5..31c666f7 100644 --- a/game/db.py +++ b/game/db.py @@ -448,6 +448,11 @@ def unitdict_split(unit_dict: UnitsDict, count: int): if len(buffer_dict): yield buffer_dict + +def unitdict_restrict_count(unit_dict: UnitsDict, total_count: int) -> UnitsDict: + return list(unitdict_split(unit_dict, total_count))[0] + + def _validate_db(): # check unit by task uniquity total_set = set() diff --git a/game/event/__init__.py b/game/event/__init__.py index f43dc91a..4f9eee7c 100644 --- a/game/event/__init__.py +++ b/game/event/__init__.py @@ -1,8 +1,8 @@ from .event import * -from .frontlinecas import * +from .frontlineattack import * from .intercept import * -from .capture import * +from .baseattack import * from .navalintercept import * from .antiaastrike import * -from .groundattack import * +from .insurgentattack import * from .infantrytransport import * diff --git a/game/event/capture.py b/game/event/baseattack.py similarity index 72% rename from game/event/capture.py rename to game/event/baseattack.py index 304895c4..b7ecbf64 100644 --- a/game/event/capture.py +++ b/game/event/baseattack.py @@ -4,13 +4,13 @@ import random from dcs.task import * from game import db -from game.operation.capture import CaptureOperation +from game.operation.baseattack import BaseAttackOperation from userdata.debriefing import Debriefing from .event import Event -class CaptureEvent(Event): +class BaseAttackEvent(Event): silent = True BONUS_BASE = 15 STRENGTH_RECOVERY = 0.55 @@ -28,7 +28,7 @@ class CaptureEvent(Event): return not attackers_success def commit(self, debriefing: Debriefing): - super(CaptureEvent, self).commit(debriefing) + super(BaseAttackEvent, self).commit(debriefing) if self.is_successfull(debriefing): if self.from_cp.captured: self.to_cp.captured = True @@ -49,13 +49,13 @@ class CaptureEvent(Event): escort = self.from_cp.base.scramble_sweep(self.game.settings.multiplier) attackers = self.from_cp.base.armor - 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 = BaseAttackOperation(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, @@ -67,13 +67,13 @@ class CaptureEvent(Event): self.operation = op def player_attacking(self, cas: db.PlaneDict, escort: db.PlaneDict, armor: db.ArmorDict, clients: db.PlaneDict): - 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 = BaseAttackOperation(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) defenders = self.to_cp.base.scramble_sweep(self.game.settings.multiplier) defenders.update(self.to_cp.base.scramble_cas(self.game.settings.multiplier)) diff --git a/game/event/frontlineattack.py b/game/event/frontlineattack.py new file mode 100644 index 00000000..b35f6d4e --- /dev/null +++ b/game/event/frontlineattack.py @@ -0,0 +1,76 @@ +import math +import random + +from dcs.task import * +from dcs.vehicles import AirDefence + +from game import * +from game.event import * +from game.operation.frontlineattack import FrontlineAttackOperation +from userdata.debriefing import Debriefing + + +class FrontlineAttackEvent(Event): + TARGET_VARIETY = 2 + TARGET_AMOUNT_FACTOR = 0.5 + ATTACKER_AMOUNT_FACTOR = 0.4 + ATTACKER_DEFENDER_FACTOR = 0.7 + STRENGTH_INFLUENCE = 0.3 + SUCCESS_MIN_TARGETS = 3 + + defenders = None # type: db.ArmorDict + + @property + def threat_description(self): + return "{} vehicles".format(self.to_cp.base.assemble_count()) + + def __str__(self): + return "Frontline attack from {} at {}".format(self.from_cp, self.to_cp) + + def is_successfull(self, debriefing: Debriefing): + total_targets = sum(self.defenders.values()) + destroyed_targets = 0 + for unit, count in debriefing.destroyed_units[self.defender_name].items(): + if unit in self.defenders: + destroyed_targets += count + + if self.from_cp.captured: + return float(destroyed_targets) >= min(self.SUCCESS_MIN_TARGETS, total_targets) + else: + return float(destroyed_targets) < min(self.SUCCESS_MIN_TARGETS, total_targets) + + def commit(self, debriefing: Debriefing): + super(FrontlineAttackEvent, 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): + if self.to_cp.captured: + self.to_cp.base.affect_strength(-0.1) + + def player_attacking(self, armor: db.ArmorDict, strikegroup: db.PlaneDict, clients: db.PlaneDict): + self.defenders = self.to_cp.base.assemble_cap() + + op = FrontlineAttackOperation(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.defenders, + attackers=db.unitdict_restrict_count(armor, sum(self.defenders.values())), + strikegroup=strikegroup) + + self.operation = op + diff --git a/game/event/frontlinecas.py b/game/event/frontlinecas.py deleted file mode 100644 index 07c3e610..00000000 --- a/game/event/frontlinecas.py +++ /dev/null @@ -1,91 +0,0 @@ -import math -import random - -from dcs.task import * -from dcs.vehicles import AirDefence - -from game import * -from game.event import * -from game.operation.frontlinecas import FrontlineCASOperation -from userdata.debriefing import Debriefing - - -class FrontlineCASEvent(Event): - TARGET_VARIETY = 2 - TARGET_AMOUNT_FACTOR = 0.5 - ATTACKER_AMOUNT_FACTOR = 0.4 - STRENGTH_INFLUENCE = 0.3 - SUCCESS_MIN_TARGETS = 3 - - targets = None # type: db.ArmorDict - - @property - def threat_description(self): - if not self.game.is_player_attack(self): - return "{} aicraft".format(self.from_cp.base.scramble_count(self.game.settings.multiplier, CAS)) - else: - return super(FrontlineCASEvent, self).threat_description - - def __str__(self): - return "Frontline CAS 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 - - if self.from_cp.captured: - return float(destroyed_targets) >= min(self.SUCCESS_MIN_TARGETS, total_targets) - else: - return float(destroyed_targets) < min(self.SUCCESS_MIN_TARGETS, total_targets) - - def commit(self, debriefing: Debriefing): - super(FrontlineCASEvent, 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): - if self.to_cp.captured: - self.to_cp.base.affect_strength(-0.1) - - def player_attacking(self, strikegroup: db.PlaneDict, clients: db.PlaneDict): - suitable_armor_targets = db.find_unittype(PinpointStrike, self.defender_name) - random.shuffle(suitable_armor_targets) - - target_types = suitable_armor_targets[:self.TARGET_VARIETY] - typecount = max(math.floor(self.to_cp.base.assemble_count() * self.TARGET_AMOUNT_FACTOR), 1) - self.targets = {unittype: typecount for unittype in target_types} - - defense_aa_unit = random.choice(self.game.commision_unit_types(self.to_cp, AirDefence)) - self.targets[defense_aa_unit] = 1 - - suitable_armor_attackers = db.find_unittype(PinpointStrike, self.attacker_name) - random.shuffle(suitable_armor_attackers) - attacker_types = suitable_armor_attackers[:self.TARGET_VARIETY] - typecount = max(math.floor(self.from_cp.base.assemble_count() * self.ATTACKER_AMOUNT_FACTOR), 1) - attackers = {unittype: typecount for unittype in attacker_types} - - op = FrontlineCASOperation(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, - attackers=attackers, - strikegroup=strikegroup) - - self.operation = op - diff --git a/game/event/groundattack.py b/game/event/insurgentattack.py similarity index 59% rename from game/event/groundattack.py rename to game/event/insurgentattack.py index 9b32523f..dbc068f7 100644 --- a/game/event/groundattack.py +++ b/game/event/insurgentattack.py @@ -5,11 +5,11 @@ from dcs.task import * from game import * from game.event import * -from game.event.frontlinecas import FrontlineCASEvent -from game.operation.groundattack import GroundAttackOperation +from game.event.frontlineattack import FrontlineAttackEvent +from game.operation.insurgentattack import InsurgentAttackOperation -class GroundAttackEvent(FrontlineCASEvent): +class InsurgentAttackEvent(FrontlineAttackEvent): def __str__(self): return "Destroy insurgents at {}".format(self.to_cp) @@ -24,13 +24,13 @@ class GroundAttackEvent(FrontlineCASEvent): typecount = max(math.floor(self.difficulty * self.TARGET_AMOUNT_FACTOR), 1) self.targets = {unittype: typecount for unittype in unittypes} - op = GroundAttackOperation(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 = InsurgentAttackOperation(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(target=self.targets, strikegroup=strikegroup) diff --git a/game/game.py b/game/game.py index 1964b4e4..7256718e 100644 --- a/game/game.py +++ b/game/game.py @@ -39,17 +39,17 @@ For the enemy events, only 1 event of each type could be generated for a turn. Events: * CaptureEvent - capture base * InterceptEvent - air intercept -* FrontlineCASEvent - frontline CAS +* FrontlineAttack - frontline attack * GroundAttackEvent - destroy insurgents * NavalInterceptEvent - naval intercept * AntiAAStrikeEvent - anti-AA strike * InfantryTransportEvent - helicopter infantry transport """ EVENT_PROBABILITIES = { - CaptureEvent: [100, 10], + BaseAttackEvent: [100, 10], InterceptEvent: [25, 10], - FrontlineCASEvent: [250, 0], - GroundAttackEvent: [0, 10], + FrontlineAttackEvent: [100, 0], + InsurgentAttackEvent: [0, 10], NavalInterceptEvent: [25, 10], AntiAAStrikeEvent: [25, 10], InfantryTransportEvent: [25, 0], @@ -130,7 +130,7 @@ class Game: if event_class == NavalInterceptEvent: if player_cp.radials == LAND: continue - elif event_class == CaptureEvent: + elif event_class == BaseAttackEvent: if enemy_cap_generated: continue if enemy_cp.base.total_armor == 0: diff --git a/game/operation/capture.py b/game/operation/baseattack.py similarity index 93% rename from game/operation/capture.py rename to game/operation/baseattack.py index 851f9488..dee129fc 100644 --- a/game/operation/capture.py +++ b/game/operation/baseattack.py @@ -12,7 +12,7 @@ from gen.visualgen import * from .operation import Operation -class CaptureOperation(Operation): +class BaseAttackOperation(Operation): cas = None # type: db.PlaneDict escort = None # type: db.PlaneDict intercept = None # type: db.PlaneDict @@ -37,7 +37,7 @@ class CaptureOperation(Operation): self.aa = aa def prepare(self, terrain: dcs.terrain.Terrain, is_quick: bool): - super(CaptureOperation, self).prepare(terrain, is_quick) + super(BaseAttackOperation, self).prepare(terrain, is_quick) self.defenders_starting_position = None if self.game.player == self.defender_name: @@ -63,5 +63,5 @@ class CaptureOperation(Operation): 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() + super(BaseAttackOperation, self).generate() diff --git a/game/operation/frontlinecas.py b/game/operation/frontlineattack.py similarity index 90% rename from game/operation/frontlinecas.py rename to game/operation/frontlineattack.py index 3f2556b4..b89d9a69 100644 --- a/game/operation/frontlinecas.py +++ b/game/operation/frontlineattack.py @@ -18,7 +18,7 @@ from .operation import Operation MAX_DISTANCE_BETWEEN_GROUPS = 12000 -class FrontlineCASOperation(Operation): +class FrontlineAttackOperation(Operation): attackers = None # type: db.ArmorDict strikegroup = None # type: db.PlaneDict target = None # type: db.ArmorDict @@ -32,7 +32,7 @@ class FrontlineCASOperation(Operation): self.attackers = attackers def prepare(self, terrain: Terrain, is_quick: bool): - super(FrontlineCASOperation, self).prepare(terrain, is_quick) + super(FrontlineAttackOperation, self).prepare(terrain, is_quick) if self.defender_name == self.game.player: self.attackers_starting_position = None self.defenders_starting_position = None @@ -51,4 +51,4 @@ class FrontlineCASOperation(Operation): def generate(self): self.armorgen.generate_vec(self.attackers, self.target) self.airgen.generate_cas_strikegroup(self.strikegroup, clients=self.attacker_clients, at=self.attackers_starting_position) - super(FrontlineCASOperation, self).generate() + super(FrontlineAttackOperation, self).generate() diff --git a/game/operation/groundattack.py b/game/operation/insurgentattack.py similarity index 87% rename from game/operation/groundattack.py rename to game/operation/insurgentattack.py index 7b12ad04..92091002 100644 --- a/game/operation/groundattack.py +++ b/game/operation/insurgentattack.py @@ -13,7 +13,7 @@ from gen.conflictgen import Conflict from .operation import Operation -class GroundAttackOperation(Operation): +class InsurgentAttackOperation(Operation): strikegroup = None # type: db.PlaneDict target = None # type: db.ArmorDict @@ -24,7 +24,7 @@ class GroundAttackOperation(Operation): self.target = target def prepare(self, terrain: Terrain, is_quick: bool): - super(GroundAttackOperation, self).prepare(terrain, is_quick) + super(InsurgentAttackOperation, self).prepare(terrain, is_quick) conflict = Conflict.ground_attack_conflict( attacker=self.mission.country(self.attacker_name), @@ -41,4 +41,4 @@ class GroundAttackOperation(Operation): self.airgen.generate_defense(self.strikegroup, self.defender_clients, self.defenders_starting_position) self.armorgen.generate(self.target, {}) - super(GroundAttackOperation, self).generate() + super(InsurgentAttackOperation, self).generate() diff --git a/gen/armor.py b/gen/armor.py index 80895b04..c6bdb4c1 100644 --- a/gen/armor.py +++ b/gen/armor.py @@ -1,3 +1,4 @@ +from random import randint from itertools import zip_longest from game import db @@ -13,6 +14,10 @@ from dcs.country import * SPREAD_DISTANCE_FACTOR = 0.1, 0.3 SPREAD_DISTANCE_SIZE_FACTOR = 0.1 +FRONTLINE_CAS_FIGHTS_COUNT = 4, 8 +FRONTLINE_CAS_GROUP_MIN = 1, 2 +FRONTLINE_CAS_PADDING = 12000 + class ArmorConflictGenerator: def __init__(self, mission: Mission, conflict: Conflict): @@ -50,7 +55,7 @@ class ArmorConflictGenerator: side=self.conflict.attackers_side, unit=type, count=count, - at=position.point_from_heading(self.conflict.heading - 90, 600), + at=position.point_from_heading(self.conflict.heading - 90, 5000), to=position) if defenders: @@ -59,7 +64,7 @@ class ArmorConflictGenerator: side=self.conflict.defenders_side, unit=type, count=count, - at=position.point_from_heading(self.conflict.heading + 90, 600), + at=position.point_from_heading(self.conflict.heading + 90, 1000), to=position) def generate(self, attackers: db.ArmorDict, defenders: db.ArmorDict): @@ -78,16 +83,16 @@ class ArmorConflictGenerator: at=self.conflict.ground_defenders_location) def generate_vec(self, attackers: db.ArmorDict, defenders: db.ArmorDict): - defender_groups = list(db.unitdict_split(defenders, 6)) - distance_between_groups = min(self.conflict.distance / len(defender_groups), 12000) - total_distance = distance_between_groups * len(defender_groups) + 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)) - attacker_groups = list(db.unitdict_split(attackers, - int(sum(attackers.values()) / len(defender_groups)))) + 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)) - position = self.conflict.center.point_from_heading(self.conflict.opposite_heading, total_distance / 2) for attacker_group_dict, target_group_dict in zip_longest(attacker_groups, defender_groups): - position = position.point_from_heading(self.conflict.heading, distance_between_groups) + position = self.conflict.position.point_from_heading(self.conflict.heading, + random.randint(FRONTLINE_CAS_PADDING, int(self.conflict.distance - FRONTLINE_CAS_PADDING))) self._generate_fight_at(attacker_group_dict, target_group_dict, position) def generate_passengers(self, count: int): diff --git a/gen/conflictgen.py b/gen/conflictgen.py index 299e6e14..3a60e0e8 100644 --- a/gen/conflictgen.py +++ b/gen/conflictgen.py @@ -38,8 +38,9 @@ NAVAL_INTERCEPT_DISTANCE_FACTOR = 1 NAVAL_INTERCEPT_DISTANCE_MAX = 40000 NAVAL_INTERCEPT_STEP = 5000 -FRONT_SMOKE_MIN_DISTANCE = 5000 -FRONT_SMOKE_DISTANCE_FACTOR = 0.5 +FRONTLINE_LENGTH = 80000 +FRONTLINE_MIN_CP_DISTANCE = 5000 +FRONTLINE_DISTANCE_STRENGTH_FACTOR = 0.7 def _opposite_heading(h): @@ -127,7 +128,7 @@ class Conflict: @classmethod def frontline_position(cls, from_cp: ControlPoint, to_cp: ControlPoint) -> typing.Tuple[Point, int]: - distance = max(from_cp.position.distance_to_point(to_cp.position) * FRONT_SMOKE_DISTANCE_FACTOR * to_cp.base.strength, FRONT_SMOKE_MIN_DISTANCE) + distance = max(from_cp.position.distance_to_point(to_cp.position) * FRONTLINE_DISTANCE_STRENGTH_FACTOR * to_cp.base.strength, FRONTLINE_MIN_CP_DISTANCE) heading = to_cp.position.heading_between_point(from_cp.position) return to_cp.position.point_from_heading(heading, distance), heading @@ -136,7 +137,7 @@ class Conflict: center_position, heading = cls.frontline_position(from_cp, to_cp) left_position = center_position - for offset in range(0, 80000, 1000): + for offset in range(0, int(FRONTLINE_LENGTH / 2), 1000): pos = center_position.point_from_heading(_heading_sum(heading, -90), offset) if not theater.is_on_land(pos): break @@ -144,7 +145,7 @@ class Conflict: left_position = pos right_position = center_position - for offset in range(0, 80000, 1000): + for offset in range(0, int(FRONTLINE_LENGTH / 2), 1000): pos = center_position.point_from_heading(_heading_sum(heading, 90), offset) if not theater.is_on_land(pos): break diff --git a/gen/visualgen.py b/gen/visualgen.py index a59c9986..175924e6 100644 --- a/gen/visualgen.py +++ b/gen/visualgen.py @@ -62,7 +62,6 @@ def __monkey_static_dict(self: Static): __original_static_dict = Static.dict Static.dict = __monkey_static_dict -FRONT_SMOKE_LENGTH = 80000 FRONT_SMOKE_SPACING = 800 FRONT_SMOKE_RANDOM_SPREAD = 4000 FRONT_SMOKE_TYPE_CHANCES = { @@ -100,9 +99,9 @@ class VisualGenerator: def _generate_frontline_smokes(self): for from_cp, to_cp in self.game.theater.conflicts(): point, heading = Conflict.frontline_position(from_cp, to_cp) - plane_start = point.point_from_heading(turn_heading(heading, 90), FRONT_SMOKE_LENGTH / 2) + plane_start = point.point_from_heading(turn_heading(heading, 90), FRONTLINE_LENGTH / 2) - for offset in range(0, FRONT_SMOKE_LENGTH, FRONT_SMOKE_SPACING): + for offset in range(0, FRONTLINE_LENGTH, FRONT_SMOKE_SPACING): position = plane_start.point_from_heading(turn_heading(heading, - 90), offset) for k, v in FRONT_SMOKE_TYPE_CHANCES.items(): diff --git a/resources/tools/miz_diff.py b/resources/tools/miz_diff.py new file mode 100644 index 00000000..500e6276 --- /dev/null +++ b/resources/tools/miz_diff.py @@ -0,0 +1,39 @@ +from dcs.lua.parse import * + + +a = loads(open("build/mission", "r").read()) +b = loads(open("build/mission_workin.lua", "r").read()) + + +def get(a, k): + b = a + for x in k.strip().split(" "): + if isinstance(a, dict): + y = a + a = a.get(x, None) + if a is None: + try: + a = y.get(int(x), None) + except: + pass + else: + break + if a is None: + pass + return a + + +def cycle(kk, ref, v): + if isinstance(v, dict): + for k, v in v.items(): + cycle(kk + " " + str(k), ref, v) + elif isinstance(v, list): + for i, v in enumerate(v): + cycle(kk + " " + str(i), ref, v) + else: + if get(ref, kk) != v: + print(kk, v) + print(get(ref, kk)) + + +cycle("", a, b) diff --git a/theater/base.py b/theater/base.py index eeed269d..ff5d815f 100644 --- a/theater/base.py +++ b/theater/base.py @@ -150,7 +150,7 @@ class Base: return min(min(max(count, PLANES_SCRAMBLE_MIN_BASE), int(PLANES_SCRAMBLE_MAX_BASE * multiplier)), count) def assemble_count(self): - return int(self.total_armor * min(self.strength + 0.5, 1)) + return int(self.total_armor * 0.5) def assemble_aa_count(self) -> int: if self.strength > STRENGTH_AA_ASSEMBLE_MIN: @@ -171,7 +171,8 @@ class Base: return self._find_best_armor(PinpointStrike, self.assemble_count()) def assemble_defense(self) -> typing.Dict[Armor, int]: - return self._find_best_armor(PinpointStrike, self.assemble_count()) + count = int(self.total_armor * min(self.strength + 0.5, 1)) + return self._find_best_armor(PinpointStrike, count) def assemble_aa(self, count=None) -> typing.Dict[AirDefence, int]: return self._find_best_unit(self.aa, AirDefence, count and min(count, self.total_aa) or self.assemble_aa_count()) diff --git a/ui/eventmenu.py b/ui/eventmenu.py index e1b2b81a..e4081975 100644 --- a/ui/eventmenu.py +++ b/ui/eventmenu.py @@ -172,8 +172,8 @@ class EventMenu(Menu): if amount > 0: scrambled_armor[unit_type] = amount - if type(self.event) is CaptureEvent: - e = self.event # type: CaptureEvent + if type(self.event) is BaseAttackEvent: + e = self.event # type: BaseAttackEvent if self.game.is_player_attack(self.event): e.player_attacking(cas=scrambled_cas, escort=scrambled_sweep, @@ -190,12 +190,9 @@ class EventMenu(Menu): else: e.player_defending(escort=scrambled_aircraft, clients=scrambled_clients) - elif type(self.event) is FrontlineCASEvent: - e = self.event # type: FrontlineCASEvent - if self.game.is_player_attack(self.event): - e.player_attacking(strikegroup=scrambled_aircraft, clients=scrambled_clients) - else: - e.player_defending(interceptors=scrambled_aircraft, clients=scrambled_clients) + elif type(self.event) is FrontlineAttackEvent: + e = self.event # type: FrontlineAttackEvent + e.player_attacking(armor=scrambled_armor, strikegroup=scrambled_aircraft, clients=scrambled_clients) elif type(self.event) is NavalInterceptEvent: e = self.event # type: NavalInterceptEvent @@ -209,8 +206,8 @@ class EventMenu(Menu): e.player_attacking(strikegroup=scrambled_aircraft, clients=scrambled_clients) else: e.player_defending(interceptors=scrambled_aircraft, clients=scrambled_clients) - elif type(self.event) is GroundAttackEvent: - e = self.event # type: GroundAttackEvent + elif type(self.event) is InsurgentAttackEvent: + e = self.event # type: InsurgentAttackEvent if self.game.is_player_attack(self.event): assert False else: diff --git a/ui/overviewcanvas.py b/ui/overviewcanvas.py index 7e0bd205..9fb58473 100644 --- a/ui/overviewcanvas.py +++ b/ui/overviewcanvas.py @@ -6,6 +6,7 @@ from tkinter.ttk import * from ui.window import * from game.game import * +from gen.conflictgen import Conflict from theater.conflicttheater import * @@ -44,7 +45,7 @@ class OverviewCanvas: def create_cp_title(self, coords, cp: ControlPoint): title = cp.name - font = ("Helvetica", 13) + font = ("Helvetica", 10) id = self.canvas.create_text(coords[0]+1, coords[1]+1, text=title, fill='white', font=font) self.canvas.tag_bind(id, "", self.display(cp)) @@ -74,9 +75,14 @@ class OverviewCanvas: self.canvas.create_line((coords[0], coords[1], connected_coords[0], connected_coords[1]), width=2, fill=color) + if cp.captured and not connected_cp.captured and Conflict.has_frontline_between(cp, connected_cp): + frontline_pos, heading, distance = Conflict.frontline_vector(cp, connected_cp, self.game.theater) + start_coords, end_coords = self.transform_point(frontline_pos), self.transform_point(frontline_pos.point_from_heading(heading, distance)) + self.canvas.create_line((*start_coords, *end_coords), width=2, fill=color) + for cp in self.game.theater.controlpoints: coords = self.transform_point(cp.position) - arc_size = 22 * math.pow(cp.importance, 1) + arc_size = 16 * math.pow(cp.importance, 1) extent = max(cp.base.strength * 180, 10) start = (180 - extent) / 2 diff --git a/userdata/persistency.py b/userdata/persistency.py index 3d40139c..f4feb69d 100644 --- a/userdata/persistency.py +++ b/userdata/persistency.py @@ -19,7 +19,7 @@ def base_path() -> str: if os.path.exists(openbeta_path): return openbeta_path else: - return os.path.join(_user_folder, "Saved Games" , "DCS") + return os.path.join(_user_folder, "Saved Games", "DCS") def _save_file() -> str: From 932bec2f84113c0e5d6ab479cf1dc8b726eb750b Mon Sep 17 00:00:00 2001 From: Vasyl Horbachenko Date: Tue, 17 Jul 2018 02:14:46 +0300 Subject: [PATCH 4/5] fixes to frontline attack; frontline CAP WIP --- game/db.py | 15 +++++- game/event/__init__.py | 1 + game/event/baseattack.py | 2 +- game/event/frontlineattack.py | 6 +-- game/event/frontlinepatrol.py | 72 +++++++++++++++++++++++++++++ game/game.py | 18 ++++++-- game/operation/antiaastrike.py | 2 +- game/operation/baseattack.py | 4 +- game/operation/frontlineattack.py | 2 +- game/operation/frontlinepatrol.py | 53 +++++++++++++++++++++ game/operation/infantrytransport.py | 2 +- game/operation/insurgentattack.py | 2 +- game/operation/intercept.py | 2 +- game/operation/operation.py | 8 ++-- gen/__init__.py | 2 +- gen/aircraft.py | 46 ++++++++++++++++-- gen/airsupportgen.py | 50 ++++++++++++++++++++ gen/awacsgen.py | 32 ------------- gen/conflictgen.py | 12 +++-- gen/naming.py | 4 ++ gen/triggergen.py | 2 +- ui/eventmenu.py | 23 +++++++++ 22 files changed, 297 insertions(+), 63 deletions(-) create mode 100644 game/event/frontlinepatrol.py create mode 100644 game/operation/frontlinepatrol.py create mode 100644 gen/airsupportgen.py delete mode 100644 gen/awacsgen.py diff --git a/game/db.py b/game/db.py index 31c666f7..f82b84f5 100644 --- a/game/db.py +++ b/game/db.py @@ -73,6 +73,8 @@ PRICES = { An_30M: 13, Yak_40: 13, S_3B_Tanker: 13, + IL_78M: 13, + KC_135: 13, A_50: 8, E_3A: 8, @@ -167,6 +169,11 @@ UNIT_BY_TASK = { C_130, ], + Refueling: [ + IL_78M, + KC_135, + ], + AWACS: [E_3A, A_50, ], PinpointStrike: [Armor.MBT_T_90, Armor.MBT_T_80U, Armor.MBT_T_55, Armor.MBT_M1A2_Abrams, Armor.MBT_M60A3_Patton, Armor.ATGM_M1134_Stryker, Armor.APC_BTR_80, ], @@ -251,6 +258,7 @@ UNIT_BY_COUNTRY = { L_39ZA, IL_76MD, + IL_78M, An_26B, An_30M, Yak_40, @@ -290,6 +298,7 @@ UNIT_BY_COUNTRY = { A_10C, AV8BNA, + KC_135, S_3B_Tanker, C_130, E_3A, @@ -450,7 +459,11 @@ def unitdict_split(unit_dict: UnitsDict, count: int): def unitdict_restrict_count(unit_dict: UnitsDict, total_count: int) -> UnitsDict: - return list(unitdict_split(unit_dict, total_count))[0] + groups = list(unitdict_split(unit_dict, total_count)) + if len(groups) > 0: + return groups[0] + else: + return {} def _validate_db(): diff --git a/game/event/__init__.py b/game/event/__init__.py index 4f9eee7c..3ee97e52 100644 --- a/game/event/__init__.py +++ b/game/event/__init__.py @@ -1,5 +1,6 @@ from .event import * from .frontlineattack import * +from .frontlinepatrol import * from .intercept import * from .baseattack import * from .navalintercept import * diff --git a/game/event/baseattack.py b/game/event/baseattack.py index b7ecbf64..e93aba31 100644 --- a/game/event/baseattack.py +++ b/game/event/baseattack.py @@ -16,7 +16,7 @@ class BaseAttackEvent(Event): STRENGTH_RECOVERY = 0.55 def __str__(self): - return "Attack from {} to {}".format(self.from_cp, self.to_cp) + return "Base attack from {} to {}".format(self.from_cp, self.to_cp) def is_successfull(self, debriefing: Debriefing): alive_attackers = sum([v for k, v in debriefing.alive_units[self.attacker_name].items() if db.unit_task(k) == PinpointStrike]) diff --git a/game/event/frontlineattack.py b/game/event/frontlineattack.py index b35f6d4e..11146809 100644 --- a/game/event/frontlineattack.py +++ b/game/event/frontlineattack.py @@ -16,7 +16,7 @@ class FrontlineAttackEvent(Event): ATTACKER_AMOUNT_FACTOR = 0.4 ATTACKER_DEFENDER_FACTOR = 0.7 STRENGTH_INFLUENCE = 0.3 - SUCCESS_MIN_TARGETS = 3 + SUCCESS_TARGETS_HIT_PERCENTAGE = 0.25 defenders = None # type: db.ArmorDict @@ -35,9 +35,9 @@ class FrontlineAttackEvent(Event): destroyed_targets += count if self.from_cp.captured: - return float(destroyed_targets) >= min(self.SUCCESS_MIN_TARGETS, total_targets) + return float(destroyed_targets) / total_targets >= self.SUCCESS_TARGETS_HIT_PERCENTAGE else: - return float(destroyed_targets) < min(self.SUCCESS_MIN_TARGETS, total_targets) + return float(destroyed_targets) / total_targets < self.SUCCESS_TARGETS_HIT_PERCENTAGE def commit(self, debriefing: Debriefing): super(FrontlineAttackEvent, self).commit(debriefing) diff --git a/game/event/frontlinepatrol.py b/game/event/frontlinepatrol.py new file mode 100644 index 00000000..ddce50f3 --- /dev/null +++ b/game/event/frontlinepatrol.py @@ -0,0 +1,72 @@ +import math +import random + +from dcs.task import * +from dcs.vehicles import AirDefence + +from game import * +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_TARGETS_HIT_PERCENTAGE = 0.6 + + 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)) + + def __str__(self): + return "Frontline CAP from {} at {}".format(self.from_cp, self.to_cp) + + def is_successfull(self, debriefing: Debriefing): + total_targets = sum(self.cas.values()) + destroyed_targets = 0 + for unit, count in debriefing.destroyed_units[self.defender_name].items(): + if unit in self.cas: + destroyed_targets += count + + if self.from_cp.captured: + return float(destroyed_targets) / total_targets >= self.SUCCESS_TARGETS_HIT_PERCENTAGE + else: + return float(destroyed_targets) / total_targets < self.SUCCESS_TARGETS_HIT_PERCENTAGE + + 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, interceptors: db.PlaneDict, clients: db.PlaneDict): + 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, + attacker_clients={}, + defender_clients=clients, + from_cp=self.from_cp, + to_cp=self.to_cp) + op.setup(cas=self.cas, + escort=self.escort, + interceptors=interceptors) + + self.operation = op diff --git a/game/game.py b/game/game.py index 7256718e..9c0d02f3 100644 --- a/game/game.py +++ b/game/game.py @@ -5,6 +5,7 @@ import math from dcs.task import * from dcs.vehicles import * +from gen.conflictgen import Conflict from userdata.debriefing import Debriefing from theater import * @@ -23,7 +24,7 @@ COMMISION_LIMITS_FACTORS = { COMMISION_AMOUNTS_SCALE = 1.5 COMMISION_AMOUNTS_FACTORS = { - PinpointStrike: 2, + PinpointStrike: 6, CAS: 1, CAP: 2, AirDefence: 0.3, @@ -31,6 +32,7 @@ COMMISION_AMOUNTS_FACTORS = { PLAYER_INTERCEPT_GLOBAL_PROBABILITY_BASE = 25 PLAYER_INTERCEPT_GLOBAL_PROBABILITY_LOG = 2 +PLAYER_BASEATTACK_THRESHOLD = 0.2 """ Various events probabilities. First key is player probabilty, second is enemy probability. @@ -47,8 +49,9 @@ Events: """ EVENT_PROBABILITIES = { BaseAttackEvent: [100, 10], - InterceptEvent: [25, 10], FrontlineAttackEvent: [100, 0], + FrontlinePatrolEvent: [1000, 0], + InterceptEvent: [25, 10], InsurgentAttackEvent: [0, 10], NavalInterceptEvent: [25, 10], AntiAAStrikeEvent: [25, 10], @@ -65,7 +68,7 @@ ENEMY_BASE_STRENGTH_RECOVERY = 0.05 AWACS_BUDGET_COST = 4 # Initial budget value -PLAYER_BUDGET_INITIAL = 120 +PLAYER_BUDGET_INITIAL = 170 # Base post-turn bonus value PLAYER_BUDGET_BASE = 10 # Bonus multiplier logarithm base @@ -112,11 +115,18 @@ class Game: continue for event_class, (player_probability, enemy_probability) in EVENT_PROBABILITIES.items(): + if event_class == FrontlineAttackEvent or event_class == InfantryTransportEvent or event_class == FrontlinePatrolEvent: + if not Conflict.has_frontline_between(player_cp, enemy_cp): + continue + if self._roll(player_probability, player_cp.base.strength): if event_class == NavalInterceptEvent and enemy_cp.radials == LAND: pass else: - self.events.append(event_class(self.player, self.enemy, player_cp, enemy_cp, self)) + if event_class == BaseAttackEvent and enemy_cp.base.strength > PLAYER_BASEATTACK_THRESHOLD: + pass + else: + self.events.append(event_class(self.player, self.enemy, player_cp, enemy_cp, self)) elif self._roll(enemy_probability, enemy_cp.base.strength): if event_class in enemy_generated_types: continue diff --git a/game/operation/antiaastrike.py b/game/operation/antiaastrike.py index 4403f504..424705a6 100644 --- a/game/operation/antiaastrike.py +++ b/game/operation/antiaastrike.py @@ -6,7 +6,7 @@ from gen.aircraft import * from gen.aaa import * from gen.shipgen import * from gen.triggergen import * -from gen.awacsgen import * +from gen.airsupportgen import * from gen.visualgen import * from gen.conflictgen import Conflict diff --git a/game/operation/baseattack.py b/game/operation/baseattack.py index dee129fc..8add9744 100644 --- a/game/operation/baseattack.py +++ b/game/operation/baseattack.py @@ -6,7 +6,7 @@ from gen.aircraft import * from gen.aaa import * from gen.shipgen import * from gen.triggergen import * -from gen.awacsgen import * +from gen.airsupportgen import * from gen.visualgen import * from .operation import Operation @@ -60,7 +60,7 @@ class BaseAttackOperation(Operation): 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.airgen.generate_attackers_escort(self.escort, clients=self.attacker_clients, at=self.attackers_starting_position) self.visualgen.generate_target_smokes(self.to_cp) super(BaseAttackOperation, self).generate() diff --git a/game/operation/frontlineattack.py b/game/operation/frontlineattack.py index b89d9a69..17513925 100644 --- a/game/operation/frontlineattack.py +++ b/game/operation/frontlineattack.py @@ -8,7 +8,7 @@ from gen.aircraft import * from gen.aaa import * from gen.shipgen import * from gen.triggergen import * -from gen.awacsgen import * +from gen.airsupportgen import * from gen.visualgen import * from gen.conflictgen import Conflict diff --git a/game/operation/frontlinepatrol.py b/game/operation/frontlinepatrol.py new file mode 100644 index 00000000..fd486747 --- /dev/null +++ b/game/operation/frontlinepatrol.py @@ -0,0 +1,53 @@ +from itertools import zip_longest + +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.triggergen import * +from gen.airsupportgen import * +from gen.visualgen import * +from gen.conflictgen import Conflict + +from .operation import Operation + + +MAX_DISTANCE_BETWEEN_GROUPS = 12000 + + +class FrontlinePatrolOperation(Operation): + cas = None # type: db.PlaneDict + escort = None # type: db.PlaneDict + interceptors = None # type: db.PlaneDict + + def setup(self, cas: db.PlaneDict, escort: db.PlaneDict, interceptors: db.PlaneDict): + self.cas = cas + self.escort = escort + self.interceptors = interceptors + + 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=self.mission.country(self.attacker_name), + defender=self.mission.country(self.defender_name), + from_cp=self.from_cp, + to_cp=self.to_cp, + theater=self.game.theater + ) + + self.initialize(mission=self.mission, + conflict=conflict) + + def generate(self): + self.airgen.generate_defenders_cas(self.cas, {}, self.defenders_starting_position) + self.airgen.generate_defenders_escort(self.escort, {}, self.defenders_starting_position) + self.airgen.generate_patrol(self.interceptors, self.defender_clients, self.attackers_starting_position) + + # todo: generate armor + + super(FrontlinePatrolOperation, self).generate() diff --git a/game/operation/infantrytransport.py b/game/operation/infantrytransport.py index 82b360f9..d327eb3a 100644 --- a/game/operation/infantrytransport.py +++ b/game/operation/infantrytransport.py @@ -6,7 +6,7 @@ from gen.aircraft import * from gen.aaa import * from gen.shipgen import * from gen.triggergen import * -from gen.awacsgen import * +from gen.airsupportgen import * from gen.visualgen import * from gen.conflictgen import Conflict diff --git a/game/operation/insurgentattack.py b/game/operation/insurgentattack.py index 92091002..e0358c68 100644 --- a/game/operation/insurgentattack.py +++ b/game/operation/insurgentattack.py @@ -6,7 +6,7 @@ from gen.aircraft import * from gen.aaa import * from gen.shipgen import * from gen.triggergen import * -from gen.awacsgen import * +from gen.airsupportgen import * from gen.visualgen import * from gen.conflictgen import Conflict diff --git a/game/operation/intercept.py b/game/operation/intercept.py index 59750f38..3121c794 100644 --- a/game/operation/intercept.py +++ b/game/operation/intercept.py @@ -52,7 +52,7 @@ class InterceptOperation(Operation): self.attackers_starting_position = ship self.airgen.generate_transport(self.transport, self.to_cp.at) - self.airgen.generate_transport_escort(self.escort, clients=self.defender_clients) + self.airgen.generate_defenders_escort(self.escort, clients=self.defender_clients) self.airgen.generate_interception(self.interceptors, clients=self.attacker_clients, at=self.attackers_starting_position) super(InterceptOperation, self).generate() diff --git a/game/operation/operation.py b/game/operation/operation.py index 4071dc69..677f3a91 100644 --- a/game/operation/operation.py +++ b/game/operation/operation.py @@ -18,7 +18,7 @@ class Operation: extra_aagen = None # type: ExtraAAConflictGenerator shipgen = None # type: ShipGenerator triggersgen = None # type: TriggersGenerator - awacsgen = None # type: AWACSConflictGenerator + awacsgen = None # type: AirSupportConflictGenerator visualgen = None # type: VisualGenerator envgen = None # type: EnvironmentGenerator @@ -52,7 +52,7 @@ class Operation: self.airgen = AircraftConflictGenerator(mission, conflict, self.game.settings) self.aagen = AAConflictGenerator(mission, conflict) self.shipgen = ShipGenerator(mission, conflict) - self.awacsgen = AWACSConflictGenerator(mission, conflict, self.game) + self.awacsgen = AirSupportConflictGenerator(mission, conflict, self.game) self.triggersgen = TriggersGenerator(mission, conflict, self.game) self.visualgen = VisualGenerator(mission, conflict, self.game) self.envgen = EnviromentGenerator(mission, conflict, self.game) @@ -78,9 +78,7 @@ class Operation: def generate(self): self.visualgen.generate() - - if self.is_awacs_enabled: - self.awacsgen.generate() + self.awacsgen.generate(self.is_awacs_enabled) self.extra_aagen.generate() self.triggersgen.generate(self.is_quick, self.trigger_radius) diff --git a/gen/__init__.py b/gen/__init__.py index 0e4e80ab..7a5565ac 100644 --- a/gen/__init__.py +++ b/gen/__init__.py @@ -1,7 +1,7 @@ from .aaa import * from .aircraft import * from .armor import * -from .awacsgen import * +from .airsupportgen import * from .conflictgen import * from .shipgen import * from .visualgen import * diff --git a/gen/aircraft.py b/gen/aircraft.py index 48ee4c8c..4c1d7cc4 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -243,13 +243,35 @@ class AircraftConflictGenerator: waypoint = group.add_waypoint(self.conflict.position, CAS_ALTITUDE, WARM_START_AIRSPEED) if self.conflict.is_vector: - group.add_waypoint(self.conflict.tail, CAS_ALTITUDE, WARM_START_ALTITUDE) + group.add_waypoint(self.conflict.tail, CAS_ALTITUDE, WARM_START_AIRSPEED) group.task = CAS.name self._setup_group(group, CAS, client_count) self.escort_targets.append((group, group.points.index(waypoint))) self._rtb_for(group, self.conflict.from_cp, at) + def generate_defenders_cas(self, defenders: 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(defenders, clients): + group = self._generate_group( + name=namegen.next_unit_name(self.conflict.defenders_side, flying_type), + side=self.conflict.defenders_side, + unit_type=flying_type, + count=count, + client_count=client_count, + at=at and at or self._group_point(self.conflict.air_defenders_location)) + + pos = self.conflict.air_defenders_location.point_from_heading(self.conflict.heading-90, CAP_CAS_DISTANCE) + waypoint = group.add_waypoint(pos, CAS_ALTITUDE, WARM_START_AIRSPEED) + if self.conflict.is_vector: + group.add_waypoint(self.conflict.tail, CAS_ALTITUDE, WARM_START_AIRSPEED) + + group.task = CAS.name + self._setup_group(group, CAS, client_count) + self.escort_targets.append((group, group.points.index(waypoint))) + self._rtb_for(group, self.conflict.to_cp, at) + def generate_ship_strikegroup(self, attackers: db.PlaneDict, clients: db.PlaneDict, target_groups: typing.Collection[ShipGroup], at: db.StartingPosition = None): assert len(self.escort_targets) == 0 @@ -271,7 +293,7 @@ class AircraftConflictGenerator: self.escort_targets.append((group, group.points.index(wayp))) self._rtb_for(group, self.conflict.from_cp, at) - def generate_strikegroup_escort(self, attackers: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None): + def generate_attackers_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, @@ -281,7 +303,7 @@ class AircraftConflictGenerator: should_orbit=True): self._rtb_for(g, self.conflict.from_cp, at) - def generate_transport_escort(self, escort: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None): + def generate_defenders_escort(self, escort: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None): for g in self._generate_escort( side=self.conflict.defenders_side, units=escort, @@ -308,6 +330,24 @@ class AircraftConflictGenerator: self._setup_group(group, CAP, client_count) self._rtb_for(group, self.conflict.to_cp, at) + def generate_patrol(self, patrol: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None): + for flying_type, count, client_count in self._split_to_groups(patrol, clients): + group = self._generate_group( + name=namegen.next_unit_name(self.conflict.attackers_side, flying_type), + 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)) + + waypoint = group.add_waypoint(self.conflict.position, WARM_START_ALTITUDE, WARM_START_AIRSPEED) + if self.conflict.is_vector: + group.add_waypoint(self.conflict.tail, WARM_START_ALTITUDE, WARM_START_AIRSPEED) + + group.task = CAP.name + self._setup_group(group, CAP, client_count) + self._rtb_for(group, self.conflict.from_cp, at) + def generate_transport(self, transport: db.PlaneDict, destination: Airport): assert len(self.escort_targets) == 0 diff --git a/gen/airsupportgen.py b/gen/airsupportgen.py new file mode 100644 index 00000000..e3e34aa3 --- /dev/null +++ b/gen/airsupportgen.py @@ -0,0 +1,50 @@ +from game import db +from .conflictgen import * +from .naming import * + +from dcs.mission import * +from dcs.unitgroup import * +from dcs.unittype import * +from dcs.task import * +from dcs.terrain.terrain import NoParkingSlotError + +TANKER_DISTANCE = 15000 +TANKER_ALT = 10000 + +AWACS_DISTANCE = 150000 +AWACS_ALT = 10000 + + +class AirSupportConflictGenerator: + def __init__(self, mission: Mission, conflict: Conflict, game): + self.mission = mission + self.conflict = conflict + self.game = game + + def generate(self, is_awacs_enabled): + tanker_unit = db.find_unittype(Refueling, self.conflict.attackers_side.name)[0] + tanker_heading = self.conflict.to_cp.position.heading_between_point(self.conflict.from_cp.position) + tanker_position = self.conflict.from_cp.position.point_from_heading(tanker_heading, TANKER_DISTANCE) + self.mission.refuel_flight( + country=self.mission.country(self.game.player), + name=namegen.next_tanker_name(self.mission.country(self.game.player)), + airport=None, + plane_type=tanker_unit, + position=tanker_position, + altitude=TANKER_ALT, + frequency=140, + start_type=StartType.Warm, + ) + + if is_awacs_enabled: + awacs_unit = db.find_unittype(AWACS, self.conflict.attackers_side.name)[0] + self.mission.awacs_flight( + country=self.mission.country(self.game.player), + name=namegen.next_awacs_name(self.mission.country(self.game.player),), + plane_type=awacs_unit, + altitude=AWACS_ALT, + airport=None, + position=self.conflict.position.random_point_within(AWACS_DISTANCE, AWACS_DISTANCE), + frequency=251, + start_type=StartType.Warm, + ) diff --git a/gen/awacsgen.py b/gen/awacsgen.py deleted file mode 100644 index 4f17fb02..00000000 --- a/gen/awacsgen.py +++ /dev/null @@ -1,32 +0,0 @@ -from game import db -from .conflictgen import * -from .naming import * - -from dcs.mission import * -from dcs.unitgroup import * -from dcs.unittype import * -from dcs.task import * -from dcs.terrain.terrain import NoParkingSlotError - -AWACS_DISTANCE = 150000 -AWACS_ALT = 10000 - - -class AWACSConflictGenerator: - def __init__(self, mission: Mission, conflict: Conflict, game): - self.mission = mission - self.conflict = conflict - self.game = game - - def generate(self): - plane = db.find_unittype(AWACS, self.conflict.attackers_side.name)[0] - - self.mission.awacs_flight( - country=self.mission.country(self.game.player), - name=namegen.next_awacs_name(self.mission.country(self.game.player),), - plane_type=plane, - altitude=AWACS_ALT, - airport=None, - position=self.conflict.position.random_point_within(AWACS_DISTANCE, AWACS_DISTANCE), - frequency=251 - ) diff --git a/gen/conflictgen.py b/gen/conflictgen.py index 3a60e0e8..ad31d529 100644 --- a/gen/conflictgen.py +++ b/gen/conflictgen.py @@ -20,6 +20,7 @@ AIR_DISTANCE = 40000 CAPTURE_AIR_ATTACKERS_DISTANCE = 25000 CAPTURE_AIR_DEFENDERS_DISTANCE = 60000 +CAP_CAS_DISTANCE = 10000 GROUND_INTERCEPT_SPREAD = 5000 GROUND_DISTANCE_FACTOR = 1 @@ -261,8 +262,11 @@ class Conflict: @classmethod def frontline_cap_conflict(cls, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater): assert cls.has_frontline_between(from_cp, to_cp) + position, heading, distance = cls.frontline_vector(from_cp, to_cp, theater) - defenders_distance = random.randint(distance/3, distance) + attack_position = position.point_from_heading(heading, randint(0, int(distance))) + attackers_position = attack_position.point_from_heading(heading - 90, AIR_DISTANCE) + defenders_position = attack_position.point_from_heading(heading + 90, CAP_CAS_DISTANCE) return cls( position=position, @@ -273,10 +277,8 @@ class Conflict: to_cp=to_cp, attackers_side=attacker, defenders_side=defender, - ground_attackers_location=None, - ground_defenders_location=None, - air_attackers_location=position.point_from_heading(random.randint(*INTERCEPT_ATTACKERS_HEADING) + heading, AIR_DISTANCE), - air_defenders_location=position.point_from_heading(heading, max(AIR_DISTANCE, defenders_distance)), + air_attackers_location=attackers_position, + air_defenders_location=defenders_position, ) @classmethod diff --git a/gen/naming.py b/gen/naming.py index 3f28e62c..f769dfb3 100644 --- a/gen/naming.py +++ b/gen/naming.py @@ -15,6 +15,10 @@ class NameGenerator: self.number += 1 return "awacs|{}|{}|0|".format(country.id, self.number) + def next_tanker_name(self, country): + self.number += 1 + return "tanker|{}|{}|0|".format(country.id, self.number) + def next_carrier_name(self, country): self.number += 1 return "carrier|{}|{}|0|".format(country.id, self.number) diff --git a/gen/triggergen.py b/gen/triggergen.py index 94b5138a..7c79f553 100644 --- a/gen/triggergen.py +++ b/gen/triggergen.py @@ -73,7 +73,7 @@ class TriggersGenerator: for country in coalition.countries.values(): if coalition_name == player_coalition: for plane_group in country.plane_group + country.helicopter_group: - if plane_group.task == AWACS.name: + if plane_group.task == AWACS.name or plane_group.task == Refueling.name: continue regroup_heading = self.conflict.to_cp.position.heading_between_point(self.conflict.from_cp.position) diff --git a/ui/eventmenu.py b/ui/eventmenu.py index e4081975..e2fe4992 100644 --- a/ui/eventmenu.py +++ b/ui/eventmenu.py @@ -1,9 +1,22 @@ +from dcs.helicopters import helicopter_map + from ui.eventresultsmenu import * from game import * from game.event import * +UNITTYPES_FOR_EVENTS = { + FrontlineAttackEvent: CAS, + FrontlinePatrolEvent: CAP, + InterceptEvent: CAP, + InsurgentAttackEvent: CAS, + NavalInterceptEvent: CAS, + AntiAAStrikeEvent: CAS, + InfantryTransportEvent: Embarking, +} + + class EventMenu(Menu): aircraft_scramble_entries = None # type: typing.Dict[PlaneType , Entry] aircraft_client_entries = None # type: typing.Dict[PlaneType, Entry] @@ -87,7 +100,14 @@ class EventMenu(Menu): Label(self.frame, text="Client slots").grid(row=row, column=3, columnspan=2) row += 1 + filter_to = UNITTYPES_FOR_EVENTS[self.event.__class__] for unit_type, count in self.base.aircraft.items(): + if filter_to and db.unit_task(unit_type) != filter_to: + continue + + if unit_type in helicopter_map and self.event.__class__ != InsurgentAttackEvent: + continue + scrable_row(unit_type, count) if not self.base.total_planes: @@ -193,6 +213,9 @@ class EventMenu(Menu): elif type(self.event) is FrontlineAttackEvent: e = self.event # type: FrontlineAttackEvent e.player_attacking(armor=scrambled_armor, strikegroup=scrambled_aircraft, clients=scrambled_clients) + elif type(self.event) is FrontlinePatrolEvent: + e = self.event # type: FrontlinePatrolEvent + e.player_attacking(interceptors=scrambled_aircraft, clients=scrambled_clients) elif type(self.event) is NavalInterceptEvent: e = self.event # type: NavalInterceptEvent From f40f83bb0995ba941167bccb7d258f3bc31a9783 Mon Sep 17 00:00:00 2001 From: Vasyl Horbachenko Date: Tue, 17 Jul 2018 05:22:41 +0300 Subject: [PATCH 5/5] fixes and improvements for fronline CAP --- game/event/frontlineattack.py | 4 ++-- game/event/frontlinepatrol.py | 12 +++++++----- game/operation/frontlinepatrol.py | 13 +++++++++---- gen/aircraft.py | 4 +++- gen/armor.py | 14 ++++++++++---- gen/environmentgen.py | 2 +- theater/base.py | 2 +- ui/eventmenu.py | 19 +++++++++++-------- 8 files changed, 44 insertions(+), 26 deletions(-) diff --git a/game/event/frontlineattack.py b/game/event/frontlineattack.py index 11146809..f89f5908 100644 --- a/game/event/frontlineattack.py +++ b/game/event/frontlineattack.py @@ -15,7 +15,7 @@ class FrontlineAttackEvent(Event): TARGET_AMOUNT_FACTOR = 0.5 ATTACKER_AMOUNT_FACTOR = 0.4 ATTACKER_DEFENDER_FACTOR = 0.7 - STRENGTH_INFLUENCE = 0.3 + STRENGTH_INFLUENCE = 0.2 SUCCESS_TARGETS_HIT_PERCENTAGE = 0.25 defenders = None # type: db.ArmorDict @@ -58,7 +58,7 @@ class FrontlineAttackEvent(Event): self.to_cp.base.affect_strength(-0.1) def player_attacking(self, armor: db.ArmorDict, strikegroup: db.PlaneDict, clients: db.PlaneDict): - self.defenders = self.to_cp.base.assemble_cap() + self.defenders = self.to_cp.base.assemble_attack() op = FrontlineAttackOperation(game=self.game, attacker_name=self.attacker_name, diff --git a/game/event/frontlinepatrol.py b/game/event/frontlinepatrol.py index ddce50f3..02fb9062 100644 --- a/game/event/frontlinepatrol.py +++ b/game/event/frontlinepatrol.py @@ -12,8 +12,8 @@ from userdata.debriefing import Debriefing class FrontlinePatrolEvent(Event): ESCORT_FACTOR = 0.5 - STRENGTH_INFLUENCE = 0.3 - SUCCESS_TARGETS_HIT_PERCENTAGE = 0.6 + STRENGTH_INFLUENCE = 0.2 + SUCCESS_TARGETS_HIT_PERCENTAGE = 0.33 cas = None # type: db.PlaneDict escort = None # type: db.PlaneDict @@ -61,12 +61,14 @@ class FrontlinePatrolEvent(Event): op = FrontlinePatrolOperation(game=self.game, attacker_name=self.attacker_name, defender_name=self.defender_name, - attacker_clients={}, - defender_clients=clients, + attacker_clients=clients, + defender_clients={}, from_cp=self.from_cp, to_cp=self.to_cp) op.setup(cas=self.cas, escort=self.escort, - interceptors=interceptors) + interceptors=interceptors, + armor_attackers=self.from_cp.base.assemble_attack(), + armor_defenders=self.to_cp.base.assemble_attack()) self.operation = op diff --git a/game/operation/frontlinepatrol.py b/game/operation/frontlinepatrol.py index fd486747..f4d7a355 100644 --- a/game/operation/frontlinepatrol.py +++ b/game/operation/frontlinepatrol.py @@ -23,11 +23,17 @@ class FrontlinePatrolOperation(Operation): escort = None # type: db.PlaneDict interceptors = None # type: db.PlaneDict - def setup(self, cas: db.PlaneDict, escort: db.PlaneDict, interceptors: db.PlaneDict): + armor_attackers = None # type: db.ArmorDict + armor_defenders = None # type: db.ArmorDict + + def setup(self, cas: db.PlaneDict, escort: db.PlaneDict, interceptors: db.PlaneDict, 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 @@ -46,8 +52,7 @@ class FrontlinePatrolOperation(Operation): def generate(self): self.airgen.generate_defenders_cas(self.cas, {}, self.defenders_starting_position) self.airgen.generate_defenders_escort(self.escort, {}, self.defenders_starting_position) - self.airgen.generate_patrol(self.interceptors, self.defender_clients, self.attackers_starting_position) - - # todo: generate armor + self.airgen.generate_patrol(self.interceptors, self.attacker_clients, self.attackers_starting_position) + self.armorgen.generate_vec(self.armor_attackers, self.armor_defenders) super(FrontlinePatrolOperation, self).generate() diff --git a/gen/aircraft.py b/gen/aircraft.py index 4c1d7cc4..3071be1a 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -264,8 +264,10 @@ class AircraftConflictGenerator: pos = self.conflict.air_defenders_location.point_from_heading(self.conflict.heading-90, CAP_CAS_DISTANCE) waypoint = group.add_waypoint(pos, CAS_ALTITUDE, WARM_START_AIRSPEED) + if self.conflict.is_vector: - group.add_waypoint(self.conflict.tail, CAS_ALTITUDE, WARM_START_AIRSPEED) + destination_tail = self.conflict.tail.distance_to_point(pos) > self.conflict.position.distance_to_point(pos) + group.add_waypoint(destination_tail and self.conflict.tail or self.conflict.position, CAS_ALTITUDE, WARM_START_AIRSPEED) group.task = CAS.name self._setup_group(group, CAS, client_count) diff --git a/gen/armor.py b/gen/armor.py index c6bdb4c1..586f4601 100644 --- a/gen/armor.py +++ b/gen/armor.py @@ -50,22 +50,28 @@ class ArmorConflictGenerator: def _generate_fight_at(self, attackers: db.ArmorDict, defenders: db.ArmorDict, position: Point): if attackers: + attack_pos = position.point_from_heading(self.conflict.heading - 90, 8000) + attack_dest = position.point_from_heading(self.conflict.heading + 90, 25000) for type, count in attackers.items(): self._generate_group( side=self.conflict.attackers_side, unit=type, count=count, - at=position.point_from_heading(self.conflict.heading - 90, 5000), - to=position) + at=attack_pos, + to=attack_dest, + ) if defenders: + def_pos = position.point_from_heading(self.conflict.heading + 90, 4000) + def_dest = position.point_from_heading(self.conflict.heading + 90, 25000) for type, count in defenders.items(): self._generate_group( side=self.conflict.defenders_side, unit=type, count=count, - at=position.point_from_heading(self.conflict.heading + 90, 1000), - to=position) + at=def_pos, + to=def_dest, + ) def generate(self, attackers: db.ArmorDict, defenders: db.ArmorDict): for type, count in attackers.items(): diff --git a/gen/environmentgen.py b/gen/environmentgen.py index 789f9d20..3e106ffd 100644 --- a/gen/environmentgen.py +++ b/gen/environmentgen.py @@ -18,7 +18,7 @@ from gen import * WEATHER_CLOUD_BASE = 2000, 3000 WEATHER_CLOUD_DENSITY = 1, 8 WEATHER_CLOUD_THICKNESS = 100, 400 -WEATHER_CLOUD_BASE_MIN = 1200 +WEATHER_CLOUD_BASE_MIN = 2400 RANDOM_TIME = { "night": 5, diff --git a/theater/base.py b/theater/base.py index ff5d815f..b6b79ebc 100644 --- a/theater/base.py +++ b/theater/base.py @@ -167,7 +167,7 @@ class Base: def scramble_interceptors(self, multiplier: float) -> typing.Dict[PlaneType, int]: return self._find_best_planes(CAP, self.scramble_count(multiplier, CAP)) - def assemble_cap(self) -> typing.Dict[Armor, int]: + def assemble_attack(self) -> typing.Dict[Armor, int]: return self._find_best_armor(PinpointStrike, self.assemble_count()) def assemble_defense(self) -> typing.Dict[Armor, int]: diff --git a/ui/eventmenu.py b/ui/eventmenu.py index e2fe4992..f699100c 100644 --- a/ui/eventmenu.py +++ b/ui/eventmenu.py @@ -7,13 +7,13 @@ from game.event import * UNITTYPES_FOR_EVENTS = { - FrontlineAttackEvent: CAS, - FrontlinePatrolEvent: CAP, - InterceptEvent: CAP, - InsurgentAttackEvent: CAS, - NavalInterceptEvent: CAS, - AntiAAStrikeEvent: CAS, - InfantryTransportEvent: Embarking, + FrontlineAttackEvent: [CAS, PinpointStrike], + FrontlinePatrolEvent: [CAP], + InterceptEvent: [CAP], + InsurgentAttackEvent: [CAS], + NavalInterceptEvent: [CAS], + AntiAAStrikeEvent: [CAS], + InfantryTransportEvent: [Embarking], } @@ -102,7 +102,7 @@ class EventMenu(Menu): filter_to = UNITTYPES_FOR_EVENTS[self.event.__class__] for unit_type, count in self.base.aircraft.items(): - if filter_to and db.unit_task(unit_type) != filter_to: + if filter_to and db.unit_task(unit_type) not in filter_to: continue if unit_type in helicopter_map and self.event.__class__ != InsurgentAttackEvent: @@ -115,6 +115,9 @@ class EventMenu(Menu): label("Armor") for unit_type, count in self.base.armor.items(): + if filter_to and db.unit_task(unit_type) not in filter_to: + continue + scramble_armor_row(unit_type, count) if not self.base.total_armor: