diff --git a/game/db.py b/game/db.py index 261764c6..c24058fe 100644 --- a/game/db.py +++ b/game/db.py @@ -239,8 +239,6 @@ UNIT_BY_COUNTRY = { M_2000C, AV8BNA, - A_10A, - A_10C, Su_25T, L_39ZA, diff --git a/game/event/capture.py b/game/event/capture.py index aebe930a..a2dee4ce 100644 --- a/game/event/capture.py +++ b/game/event/capture.py @@ -12,24 +12,12 @@ from .event import Event class CaptureEvent(Event): silent = True - BONUS_BASE = 10 + BONUS_BASE = 15 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.game.settings.multiplier), - 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([v for k, v in debriefing.alive_units[self.attacker_name].items() if db.unit_task(k) == PinpointStrike]) alive_defenders = sum([v for k, v in debriefing.alive_units[self.defender_name].items() if db.unit_task(k) == PinpointStrike]) diff --git a/game/event/event.py b/game/event/event.py index 7a846cc7..a2fc3f3a 100644 --- a/game/event/event.py +++ b/game/event/event.py @@ -18,7 +18,7 @@ class Event: difficulty = 1 # type: int game = None # type: Game environment_settings = None # type: EnvironmentSettings - BONUS_BASE = 3 + BONUS_BASE = 5 def __init__(self, attacker_name: str, defender_name: str, from_cp: ControlPoint, to_cp: ControlPoint, game): self.attacker_name = attacker_name diff --git a/game/event/groundintercept.py b/game/event/groundintercept.py index 5e4f5e50..5810de5d 100644 --- a/game/event/groundintercept.py +++ b/game/event/groundintercept.py @@ -18,6 +18,13 @@ class GroundInterceptEvent(Event): 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 "Fontline CAS from {} at {}".format(self.from_cp, self.to_cp) diff --git a/game/event/infantrytransport.py b/game/event/infantrytransport.py index 8149adfa..cc1d9652 100644 --- a/game/event/infantrytransport.py +++ b/game/event/infantrytransport.py @@ -40,6 +40,8 @@ class InfantryTransportEvent(Event): to_cp=self.to_cp ) - op.setup(transport=transport) + air_defense = db.find_unittype(AirDefence, self.defender_name)[0] + op.setup(transport=transport, + aa={air_defense: 2}) self.operation = op diff --git a/game/event/intercept.py b/game/event/intercept.py index 75d80a6b..3497a519 100644 --- a/game/event/intercept.py +++ b/game/event/intercept.py @@ -13,7 +13,6 @@ from .event import Event class InterceptEvent(Event): - BONUS_BASE = 5 STRENGTH_INFLUENCE = 0.3 GLOBAL_STRENGTH_INFLUENCE = 0.3 AIRDEFENSE_COUNT = 3 @@ -25,7 +24,7 @@ class InterceptEvent(Event): @property def threat_description(self): - return "{} aircraft".format(self.enemy_cp.base.scramble_count(self.game.settings.multiplier)) + return "{} aircraft".format(self.enemy_cp.base.scramble_count(self.game.settings.multiplier, CAP)) def is_successfull(self, debriefing: Debriefing): units_destroyed = debriefing.destroyed_units[self.defender_name].get(self.transport_unit, 0) diff --git a/game/game.py b/game/game.py index fe1f4450..3bbcf6f1 100644 --- a/game/game.py +++ b/game/game.py @@ -64,7 +64,7 @@ AWACS_BUDGET_COST = 4 # Initial budget value PLAYER_BUDGET_INITIAL = 120 # Base post-turn bonus value -PLAYER_BUDGET_BASE = 30 +PLAYER_BUDGET_BASE = 10 # Bonus multiplier logarithm base PLAYER_BUDGET_IMPORTANCE_LOG = 2 diff --git a/game/operation/infantrytransport.py b/game/operation/infantrytransport.py index 2b259758..82b360f9 100644 --- a/game/operation/infantrytransport.py +++ b/game/operation/infantrytransport.py @@ -15,9 +15,11 @@ from .operation import Operation class InfantryTransportOperation(Operation): transport = None # type: db.HeliDict + aa = None # type: db.AirDefenseDict - def setup(self, transport: db.HeliDict): + def setup(self, transport: db.HeliDict, aa: db.AirDefenseDict): self.transport = transport + self.aa = aa def prepare(self, terrain: Terrain, is_quick: bool): super(InfantryTransportOperation, self).prepare(terrain, is_quick) @@ -40,7 +42,8 @@ class InfantryTransportOperation(Operation): at=self.attackers_starting_position ) - self.armorgen.generate_passengers(count=8) + self.armorgen.generate_passengers(count=6) + self.aagen.generate_at_defenders_location(self.aa) self.visualgen.generate_transportation_marker(self.conflict.ground_attackers_location) self.visualgen.generate_transportation_destination(self.conflict.position) diff --git a/gen/aaa.py b/gen/aaa.py index 9cc5afb4..84dbf83c 100644 --- a/gen/aaa.py +++ b/gen/aaa.py @@ -16,6 +16,16 @@ class AAConflictGenerator: self.m = mission self.conflict = conflict + def generate_at_defenders_location(self, units: db.AirDefenseDict): + for unit_type, count in units.items(): + for _ in range(count): + self.m.vehicle_group( + country=self.conflict.defenders_side, + name=namegen.next_ground_group_name(), + _type=unit_type, + position=self.conflict.ground_defenders_location.random_point_within(100, 100), + group_size=1) + def generate(self, units: db.AirDefenseDict): for type, count in units.items(): for _, radial in zip(range(count), self.conflict.radials): diff --git a/gen/aircraft.py b/gen/aircraft.py index 1dc7566b..3cf12635 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -23,7 +23,8 @@ HELI_ALT = 900 WARM_START_AIRSPEED = 550 INTERCEPTION_AIRSPEED = 1000 -INTERCEPT_MAX_DISTANCE = 80000 +DEFENCE_ENGAGEMENT_MAX_DISTANCE = 60000 +INTERCEPT_MAX_DISTANCE = 200000 class AircraftConflictGenerator: @@ -87,7 +88,7 @@ class AircraftConflictGenerator: else: group.units[idx].set_client() - group.points[0].tasks.append(OptReactOnThreat(OptReactOnThreat.Values.ByPassAndEscape)) + group.points[0].tasks.append(OptReactOnThreat(OptReactOnThreat.Values.EvadeFire)) def _generate_at_airport(self, name: str, side: Country, unit_type: FlyingType, count: int, client_count: int, airport: Airport = None) -> FlyingGroup: assert count > 0 @@ -266,7 +267,7 @@ class AircraftConflictGenerator: group.task = CAP.name wayp = group.add_waypoint(self.conflict.position, CAS_ALTITUDE, WARM_START_AIRSPEED) - wayp.tasks.append(dcs.task.EngageTargets(max_distance=INTERCEPT_MAX_DISTANCE)) + wayp.tasks.append(dcs.task.EngageTargets(max_distance=DEFENCE_ENGAGEMENT_MAX_DISTANCE)) wayp.tasks.append(dcs.task.OrbitAction()) self._setup_group(group, CAP, client_count) @@ -307,7 +308,7 @@ class AircraftConflictGenerator: initial_wayp = group.add_waypoint(group.position.point_from_heading(heading, WORKAROUND_WAYP_DIST), INTERCEPTION_ALT, INTERCEPTION_AIRSPEED) initial_wayp.tasks.append(EngageTargets(max_distance=INTERCEPT_MAX_DISTANCE)) - wayp = group.add_waypoint(self.conflict.position, 0) + wayp = group.add_waypoint(self.conflict.position, WARM_START_ALTITUDE, INTERCEPTION_AIRSPEED) wayp.tasks.append(EngageTargets(max_distance=INTERCEPT_MAX_DISTANCE)) self._setup_group(group, CAP, client_count) diff --git a/gen/conflictgen.py b/gen/conflictgen.py index a0b5fe06..acb1e629 100644 --- a/gen/conflictgen.py +++ b/gen/conflictgen.py @@ -23,6 +23,7 @@ AIR_DISTANCE = 32000 INTERCEPT_ATTACKERS_HEADING = -45, 45 INTERCEPT_DEFENDERS_HEADING = -10, 10 +INTERCEPT_CONFLICT_DISTANCE = 50000 INTERCEPT_ATTACKERS_DISTANCE = 100000 INTERCEPT_MAX_DISTANCE = 160000 INTERCEPT_MIN_DISTANCE = 100000 @@ -41,7 +42,7 @@ def _heading_sum(h, a) -> int: if h > 360: return h - 360 elif h < 0: - return 360 - h + return 360 + h else: return h @@ -101,7 +102,8 @@ class Conflict: @classmethod def capture_conflict(cls, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater): position = to_cp.position - attack_heading = to_cp.find_radial(to_cp.position.heading_between_point(from_cp.position)) + attack_raw_heading = to_cp.position.heading_between_point(from_cp.position) + attack_heading = to_cp.find_radial(attack_raw_heading) defense_heading = to_cp.find_radial(from_cp.position.heading_between_point(to_cp.position), ignored_radial=attack_heading) distance = to_cp.size * GROUND_DISTANCE_FACTOR @@ -120,8 +122,8 @@ class Conflict: defenders_side=defender, ground_attackers_location=attackers_location, ground_defenders_location=defenders_location, - air_attackers_location=position.point_from_heading(attack_heading, AIR_DISTANCE), - air_defenders_location=position.point_from_heading(defense_heading, AIR_DISTANCE) + air_attackers_location=position.point_from_heading(attack_raw_heading, AIR_DISTANCE), + air_defenders_location=position.point_from_heading(_opposite_heading(attack_raw_heading), AIR_DISTANCE) ) @classmethod @@ -133,7 +135,7 @@ class Conflict: position = from_cp.position.point_from_heading(heading, distance) return cls( - position=position, + position=position.point_from_heading(position.heading_between_point(to_cp.position), INTERCEPT_CONFLICT_DISTANCE), theater=theater, from_cp=from_cp, to_cp=to_cp, @@ -213,27 +215,6 @@ class Conflict: air_defenders_location=position ) - @classmethod - def intercept_conflict(cls, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater): - raw_distance = from_cp.position.distance_to_point(to_cp.position) - distance = max(min(raw_distance, INTERCEPT_MAX_DISTANCE), INTERCEPT_MIN_DISTANCE) - - heading = _heading_sum(from_cp.position.heading_between_point(to_cp.position), random.choice([-1, 1]) * random.randint(60, 100)) - position = from_cp.position.point_from_heading(heading, distance) - - return cls( - position=position, - 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, INTERCEPT_ATTACKERS_DISTANCE), - air_defenders_location=position - ) - @classmethod def naval_intercept_conflict(cls, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater): radial = random.choice(to_cp.sea_radials) diff --git a/gen/environmentgen.py b/gen/environmentgen.py index 6b739fd3..6a419d07 100644 --- a/gen/environmentgen.py +++ b/gen/environmentgen.py @@ -28,9 +28,9 @@ RANDOM_TIME = { RANDOM_WEATHER = { 1: 5, # heavy rain - 2: 30, # rain - 3: 40, # dynamic - 4: 50, # clear + 2: 20, # rain + 3: 30, # dynamic + 4: 40, # clear 5: 100, # random } diff --git a/theater/base.py b/theater/base.py index 57fd0980..4652532f 100644 --- a/theater/base.py +++ b/theater/base.py @@ -12,7 +12,7 @@ from dcs.task import * STRENGTH_AA_ASSEMBLE_MIN = 0.2 PLANES_SCRAMBLE_MIN_BASE = 4 PLANES_SCRAMBLE_MAX_BASE = 8 -PLANES_SCRAMBLE_FACTOR = 0.8 +PLANES_SCRAMBLE_FACTOR = 0.6 class Base: @@ -140,9 +140,14 @@ class Base: elif self.strength < 0: self.strength = 0.001 - def scramble_count(self, multiplier: float) -> int: - count = int(math.ceil(self.total_planes * PLANES_SCRAMBLE_FACTOR * self.strength)) - return min(min(max(count, PLANES_SCRAMBLE_MIN_BASE), int(PLANES_SCRAMBLE_MAX_BASE * multiplier)), self.total_planes) + def scramble_count(self, multiplier: float, task: Task = None) -> int: + if task: + count = sum([v for k, v in self.aircraft.items() if db.unit_task(k) == task]) + else: + count = self.total_planes + + count = int(math.ceil(count * PLANES_SCRAMBLE_FACTOR * self.strength)) + 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 * self.strength) @@ -154,13 +159,13 @@ class Base: return 0 def scramble_sweep(self, multiplier: float) -> typing.Dict[PlaneType, int]: - return self._find_best_planes(CAP, self.scramble_count(multiplier)) + return self._find_best_planes(CAP, self.scramble_count(multiplier, CAP)) def scramble_cas(self, multiplier: float) -> typing.Dict[PlaneType, int]: - return self._find_best_planes(CAS, self.scramble_count(multiplier)) + return self._find_best_planes(CAS, self.scramble_count(multiplier, CAS)) def scramble_interceptors(self, multiplier: float) -> typing.Dict[PlaneType, int]: - return self._find_best_planes(CAP, self.scramble_count(multiplier)) + return self._find_best_planes(CAP, self.scramble_count(multiplier, CAP)) def assemble_cap(self) -> typing.Dict[Armor, int]: return self._find_best_armor(PinpointStrike, self.assemble_count()) diff --git a/ui/basemenu.py b/ui/basemenu.py index 37b7a568..8cc15640 100644 --- a/ui/basemenu.py +++ b/ui/basemenu.py @@ -27,9 +27,11 @@ class BaseMenu(Menu): scheduled_units = self.event.units.get(unit_type, 0) Label(self.frame, text="{}".format(db.unit_type_name(unit_type))).grid(row=row, sticky=W) + label = Label(self.frame, text="({})".format(existing_units)) - self.bought_amount_labels[unit_type] = label label.grid(column=1, row=row) + self.bought_amount_labels[unit_type] = label + Label(self.frame, text="{}m".format(unit_price)).grid(column=2, row=row) Button(self.frame, text="+", command=self.buy(unit_type)).grid(column=3, row=row) Button(self.frame, text="-", command=self.sell(unit_type)).grid(column=4, row=row) @@ -62,15 +64,22 @@ class BaseMenu(Menu): super(BaseMenu, self).dismiss() + def _update_count_label(self, unit_type: UnitType): + self.bought_amount_labels[unit_type]["text"] = "({}{})".format( + self.cp.base.total_units_of_type(unit_type), + unit_type in self.event.units and ", bought {}".format(self.event.units[unit_type]) or "" + ) + + self.budget_label["text"] = "Budget: {}m".format(self.game.budget) + def buy(self, unit_type): def action(): price = db.PRICES[unit_type] if self.game.budget >= price: self.event.deliver({unit_type: 1}) self.game.budget -= price - label = self.bought_amount_labels[unit_type] # type: Label - label["text"] = "({}, bought {})".format(self.cp.base.total_units_of_type(unit_type), self.event.units[unit_type]) - self.budget_label["text"] = "Budget: {}m".format(self.game.budget) + + self._update_count_label(unit_type) return action @@ -87,8 +96,6 @@ class BaseMenu(Menu): self.game.budget += price self.base.commit_losses({unit_type: 1}) - label = self.bought_amount_labels[unit_type] # type: Label - label["text"] = "({}, bought {})".format(self.cp.base.total_units_of_type(unit_type), self.event.units[unit_type]) - self.budget_label["text"] = "Budget: {}m".format(self.game.budget) + self._update_count_label(unit_type) return action \ No newline at end of file diff --git a/userdata/debriefing.py b/userdata/debriefing.py index 87d2adc9..2a247174 100644 --- a/userdata/debriefing.py +++ b/userdata/debriefing.py @@ -92,7 +92,7 @@ class Debriefing: def debriefing_directory_location() -> str: - return os.path.expanduser("~\Saved Games\DCS") + return os.path.expanduser("~\Saved Games\DCS\liberation_debriefings") def _logfiles_snapshot() -> typing.Dict[str, float]: @@ -109,14 +109,7 @@ def _poll_new_debriefing_log(snapshot: typing.Dict[str, float], callback: typing while should_run: for file, timestamp in _logfiles_snapshot().items(): if file not in snapshot or timestamp != snapshot[file]: - for _ in range(0, 3): - # some solid programming going on in here - try: - debriefing = Debriefing.parse(os.path.join(debriefing_directory_location(), file)) - break - except: - time.sleep(3) - + debriefing = Debriefing.parse(os.path.join(debriefing_directory_location(), file)) callback(debriefing) should_run = False break