diff --git a/game/db.py b/game/db.py index 422f00c2..4721f5fb 100644 --- a/game/db.py +++ b/game/db.py @@ -39,7 +39,6 @@ and prioritization for the enemy (i.e. less important bases will receive units w """ PRICES = { # fighter - C_101CC: 8, MiG_23MLD: 18, Su_27: 20, Su_33: 22, @@ -85,14 +84,14 @@ PRICES = { C_130: 8, # armor - Armor.APC_BTR_80: 12, - Armor.MBT_T_55: 14, - Armor.MBT_T_80U: 18, - Armor.MBT_T_90: 20, + Armor.APC_BTR_80: 16, + Armor.MBT_T_55: 22, + Armor.MBT_T_80U: 28, + Armor.MBT_T_90: 35, - Armor.ATGM_M1134_Stryker: 12, - Armor.MBT_M60A3_Patton: 14, - Armor.MBT_M1A2_Abrams: 18, + Armor.ATGM_M1134_Stryker: 18, + Armor.MBT_M60A3_Patton: 24, + Armor.MBT_M1A2_Abrams: 35, Unarmed.Transport_UAZ_469: 3, Unarmed.Transport_Ural_375: 3, @@ -137,7 +136,6 @@ Following tasks are present: """ UNIT_BY_TASK = { CAP: [ - C_101CC, F_5E_3, MiG_23MLD, Su_27, @@ -258,7 +256,6 @@ Be advised that putting unit to the country that have not access to the unit in """ UNIT_BY_COUNTRY = { "Russia": [ - C_101CC, AJS37, MiG_23MLD, F_5E_3, diff --git a/game/event/baseattack.py b/game/event/baseattack.py index 5e7ca266..77a7cb03 100644 --- a/game/event/baseattack.py +++ b/game/event/baseattack.py @@ -25,8 +25,8 @@ class BaseAttackEvent(Event): return "Ground attack" 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]) + alive_attackers = sum([v for k, v in debriefing.alive_units.get(self.attacker_name, {}).items() if db.unit_task(k) == PinpointStrike]) + alive_defenders = sum([v for k, v in debriefing.alive_units.get(self.defender_name, {}).items() if db.unit_task(k) == PinpointStrike]) attackers_success = alive_attackers >= alive_defenders if self.departure_cp.captured: return attackers_success diff --git a/game/event/convoystrike.py b/game/event/convoystrike.py index 4b9c2cc8..5aea7940 100644 --- a/game/event/convoystrike.py +++ b/game/event/convoystrike.py @@ -53,7 +53,7 @@ class ConvoyStrikeEvent(Event): self.from_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) def is_successfull(self, debriefing: Debriefing): - killed_units = sum([v for k, v in debriefing.destroyed_units[self.defender_name].items() if db.unit_task(k) in [PinpointStrike, Reconnaissance]]) + killed_units = sum([v for k, v in debriefing.destroyed_units.get(self.defender_name, {}).items() if db.unit_task(k) in [PinpointStrike, Reconnaissance]]) all_units = sum(self.targets.values()) attackers_success = (float(killed_units) / (all_units + 0.01)) > self.SUCCESS_FACTOR if self.from_cp.captured: diff --git a/game/event/event.py b/game/event/event.py index d5bc9be8..9f7ac89f 100644 --- a/game/event/event.py +++ b/game/event/event.py @@ -16,6 +16,7 @@ from userdata.debriefing import Debriefing from userdata import persistency DIFFICULTY_LOG_BASE = 1.1 +EVENT_DEPARTURE_MAX_DISTANCE = 340000 class Event: @@ -74,6 +75,18 @@ class Event: def global_cp_available(self) -> bool: return False + def is_departure_available_from(self, cp: ControlPoint) -> bool: + if not cp.captured: + return False + + if self.location.distance_to_point(cp.position) > EVENT_DEPARTURE_MAX_DISTANCE: + return False + + if cp.is_global and not self.global_cp_available: + return False + + return True + def bonus(self) -> int: return int(math.log(self.to_cp.importance + 1, DIFFICULTY_LOG_BASE) * self.BONUS_BASE) diff --git a/game/event/frontlineattack.py b/game/event/frontlineattack.py index 91c35088..501a6a12 100644 --- a/game/event/frontlineattack.py +++ b/game/event/frontlineattack.py @@ -18,7 +18,7 @@ class FrontlineAttackEvent(Event): @property def tasks(self) -> typing.Collection[typing.Type[Task]]: if self.is_player_attacking: - return [CAS] + return [CAS, CAP] else: return [CAP] @@ -38,8 +38,8 @@ class FrontlineAttackEvent(Event): return "Frontline attack" 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]) + alive_attackers = sum([v for k, v in debriefing.alive_units.get(self.attacker_name, {}).items() if db.unit_task(k) == PinpointStrike]) + alive_defenders = sum([v for k, v in debriefing.alive_units.get(self.defender_name, {}).items() if db.unit_task(k) == PinpointStrike]) attackers_success = (float(alive_attackers) / (alive_defenders + 0.01)) > self.SUCCESS_FACTOR if self.from_cp.captured: return attackers_success @@ -65,8 +65,7 @@ class FrontlineAttackEvent(Event): self.to_cp.base.affect_strength(-0.1) def player_attacking(self, flights: db.TaskForceDict): - assert CAS in flights and len(flights) == 1, "Invalid flights" - + assert CAS in flights and CAP in flights and len(flights) == 2, "Invalid flights" op = FrontlineAttackOperation(game=self.game, attacker_name=self.attacker_name, @@ -76,10 +75,36 @@ class FrontlineAttackEvent(Event): to_cp=self.to_cp) defenders = self.to_cp.base.assemble_attack() - attackers = db.unitdict_restrict_count(self.from_cp.base.assemble_attack(), sum(defenders.values())) - op.setup(target=defenders, + max_attackers = int(math.ceil(sum(defenders.values()) * self.ATTACKER_DEFENDER_FACTOR)) + attackers = db.unitdict_restrict_count(self.from_cp.base.assemble_attack(), max_attackers) + op.setup(defenders=defenders, attackers=attackers, - strikegroup=flights[CAS]) + strikegroup=flights[CAS], + escort=flights[CAP], + interceptors=assigned_units_from(self.to_cp.base.scramble_interceptors(1))) + + self.operation = op + + def player_defending(self, flights: db.TaskForceDict): + assert CAP in flights and len(flights) == 1, "Invalid flights" + + op = FrontlineAttackOperation(game=self.game, + attacker_name=self.attacker_name, + defender_name=self.defender_name, + from_cp=self.from_cp, + departure_cp=self.departure_cp, + to_cp=self.to_cp) + + defenders = self.to_cp.base.assemble_attack() + + max_attackers = int(math.ceil(sum(defenders.values()))) + attackers = db.unitdict_restrict_count(self.from_cp.base.assemble_attack(), max_attackers) + + op.setup(defenders=defenders, + attackers=attackers, + strikegroup=assigned_units_from(self.from_cp.base.scramble_cas(1)), + escort=assigned_units_from(self.from_cp.base.scramble_sweep(1)), + interceptors=flights[CAP]) self.operation = op diff --git a/game/event/intercept.py b/game/event/intercept.py index 0c424344..9c14e753 100644 --- a/game/event/intercept.py +++ b/game/event/intercept.py @@ -42,8 +42,8 @@ class InterceptEvent(Event): return True def is_successfull(self, debriefing: Debriefing): - units_destroyed = debriefing.destroyed_units[self.defender_name].get(self.transport_unit, 0) - if self.departure_cp.captured: + units_destroyed = debriefing.destroyed_units.get(self.defender_name, {}).get(self.transport_unit, 0) + if self.from_cp.captured: return units_destroyed > 0 else: return units_destroyed == 0 @@ -56,11 +56,11 @@ class InterceptEvent(Event): for _, cp in self.game.theater.conflicts(True): cp.base.affect_strength(-self.STRENGTH_INFLUENCE) else: - self.departure_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) + self.from_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) else: # enemy attacking if self.is_successfull(debriefing): - self.departure_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) + self.from_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) else: self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) @@ -95,7 +95,7 @@ class InterceptEvent(Event): def player_defending(self, flights: db.TaskForceDict): assert CAP in flights and len(flights) == 1, "Invalid flights" - interceptors = self.departure_cp.base.scramble_interceptors(self.game.settings.multiplier) + interceptors = self.from_cp.base.scramble_interceptors(self.game.settings.multiplier) self.transport_unit = random.choice(db.find_unittype(Transport, self.defender_name)) assert self.transport_unit is not None @@ -107,7 +107,8 @@ class InterceptEvent(Event): departure_cp=self.departure_cp, to_cp=self.to_cp) - op.setup(escort=flights[CAP], + op.setup(location=self.location, + escort=flights[CAP], transport={self.transport_unit: 1}, interceptors=assigned_units_from(interceptors), airdefense={}) diff --git a/game/event/navalintercept.py b/game/event/navalintercept.py index f1d335ce..f29bcb8a 100644 --- a/game/event/navalintercept.py +++ b/game/event/navalintercept.py @@ -49,7 +49,7 @@ class NavalInterceptEvent(Event): 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(): + for unit, count in debriefing.destroyed_units.get(self.defender_name, {}).items(): if unit in self.targets: destroyed_targets += count diff --git a/game/game.py b/game/game.py index 6b4502f0..3d016457 100644 --- a/game/game.py +++ b/game/game.py @@ -43,19 +43,18 @@ Events: * BaseAttackEvent - capture base * InterceptEvent - air intercept * FrontlineAttackEvent - frontline attack -* FrontlineCAPEvent - frontline attack * NavalInterceptEvent - naval intercept * StrikeEvent - strike event * InfantryTransportEvent - helicopter infantry transport """ EVENT_PROBABILITIES = { # events always present; only for the player - FrontlineAttackEvent: [100, 0], + FrontlineAttackEvent: [100, 9], #FrontlinePatrolEvent: [100, 0], StrikeEvent: [100, 0], # events randomly present; only for the player - InfantryTransportEvent: [25, 0], + #InfantryTransportEvent: [25, 0], ConvoyStrikeEvent: [25, 0], # events conditionally present; for both enemy and player @@ -163,6 +162,8 @@ class Game: self.events.append(event_class(self, enemy_cp, player_cp, player_cp.position, self.enemy, self.player)) def _generate_events(self): + strikes_generated_for = set() + for player_cp, enemy_cp in self.theater.conflicts(True): for event_class, (player_probability, enemy_probability) in EVENT_PROBABILITIES.items(): if event_class in [FrontlineAttackEvent, FrontlinePatrolEvent, InfantryTransportEvent, ConvoyStrikeEvent]: @@ -170,8 +171,15 @@ class Game: if not Conflict.has_frontline_between(player_cp, enemy_cp): continue + if event_class in [StrikeEvent]: + # don't generate multiple 100% strike events from each attack direction + if enemy_cp in strikes_generated_for: + continue + if player_probability == 100 or player_probability > 0 and self._roll(player_probability, player_cp.base.strength): self._generate_player_event(event_class, player_cp, enemy_cp) + if event_class in [StrikeEvent]: + strikes_generated_for.add(enemy_cp) if enemy_probability == 100 or enemy_probability > 0 and self._roll(enemy_probability, enemy_cp.base.strength): self._generate_enemy_event(event_class, player_cp, enemy_cp) diff --git a/game/operation/frontlineattack.py b/game/operation/frontlineattack.py index eb9b41f9..a9272cb6 100644 --- a/game/operation/frontlineattack.py +++ b/game/operation/frontlineattack.py @@ -7,16 +7,24 @@ MAX_DISTANCE_BETWEEN_GROUPS = 12000 class FrontlineAttackOperation(Operation): + interceptors = None # type: db.AssignedUnitsDict + escort = None # type: db.AssignedUnitsDict strikegroup = None # type: db.AssignedUnitsDict + attackers = None # type: db.ArmorDict - target = None # type: db.ArmorDict + defenders = None # type: db.ArmorDict def setup(self, - target: db.ArmorDict, + defenders: db.ArmorDict, attackers: db.ArmorDict, - strikegroup: db.AssignedUnitsDict): + strikegroup: db.AssignedUnitsDict, + escort: db.AssignedUnitsDict, + interceptors: db.AssignedUnitsDict): self.strikegroup = strikegroup - self.target = target + self.escort = escort + self.interceptors = interceptors + + self.defenders = defenders self.attackers = attackers def prepare(self, terrain: Terrain, is_quick: bool): @@ -40,8 +48,10 @@ class FrontlineAttackOperation(Operation): if self.is_player_attack: self.prepare_carriers(db.unitdict_from(self.strikegroup)) - self.armorgen.generate_vec(self.attackers, self.target) + # ground units + self.armorgen.generate_vec(self.attackers, self.defenders) + # strike group w/ heli support planes_flights = {k: v for k, v in self.strikegroup.items() if k in plane_map.values()} self.airgen.generate_cas_strikegroup(*assigned_units_split(planes_flights), at=self.attackers_starting_position) @@ -54,6 +64,10 @@ class FrontlineAttackOperation(Operation): at=farp, escort=len(planes_flights) == 0) + self.airgen.generate_attackers_escort(*assigned_units_split(self.escort), at=self.attackers_starting_position) + + self.airgen.generate_defense(*assigned_units_split(self.interceptors), at=self.defenders_starting_position) + self.briefinggen.title = "Frontline CAS" self.briefinggen.description = "Provide CAS for the ground forces attacking enemy lines. Operation will be considered successful if total number of enemy units will be lower than your own by a factor of 1.5 (i.e. with 12 units from both sides, enemy forces need to be reduced to at least 8), meaning that you (and, probably, your wingmans) should concentrate on destroying the enemy units. Target base strength will be lowered as a result. Be advised that your flight will not attack anything until you explicitly tell them so by comms menu." self.briefinggen.append_waypoint("CAS AREA IP") diff --git a/gen/aircraft.py b/gen/aircraft.py index 576552fb..ae0173d5 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -17,23 +17,23 @@ ESCORT_ENGAGEMENT_MAX_DIST = 100000 WORKAROUND_WAYP_DIST = 1000 WARM_START_HELI_AIRSPEED = 120 -WARM_START_HELI_ALT = 1000 +WARM_START_HELI_ALT = 500 WARM_START_ALTITUDE = 3000 WARM_START_AIRSPEED = 550 -INTERCEPTION_ALT = 3000 INTERCEPTION_AIRSPEED = 1000 BARCAP_RACETRACK_DISTANCE = 20000 -ATTACK_CIRCLE_ALT = 5000 +ATTACK_CIRCLE_ALT = 1000 ATTACK_CIRCLE_DURATION = 15 -CAS_ALTITUDE = 1000 -RTB_ALTITUDE = 3000 -HELI_ALT = 900 +CAS_ALTITUDE = 800 +RTB_ALTITUDE = 800 +RTB_DISTANCE = 5000 +HELI_ALT = 500 -TRANSPORT_LANDING_ALT = 1000 +TRANSPORT_LANDING_ALT = 2000 DEFENCE_ENGAGEMENT_MAX_DISTANCE = 60000 INTERCEPT_MAX_DISTANCE = 200000 @@ -149,7 +149,7 @@ class AircraftConflictGenerator: pos = Point(at.x + random.randint(100, 1000), at.y + random.randint(100, 1000)) logging.info("airgen: {} for {} at {} at {}".format(unit_type, side.id, alt, speed)) - return self.m.flight_group( + group = self.m.flight_group( country=side, name=name, aircraft_type=unit_type, @@ -161,6 +161,9 @@ class AircraftConflictGenerator: start_type=self._start_type(), group_size=count) + group.points[0].alt_type = "RADIO" + return group + def _generate_at_group(self, name: str, side: Country, unit_type: FlyingType, count: int, client_count: int, at: typing.Union[ShipGroup, StaticGroup]) -> FlyingGroup: assert count > 0 assert unit is not None @@ -197,17 +200,26 @@ class AircraftConflictGenerator: else: assert False + def _add_radio_waypoint(self, group: FlyingGroup, position, altitude: int, airspeed: int = 600): + point = group.add_waypoint(position, altitude, airspeed) + point.alt_type = "RADIO" + return point + def _rtb_for(self, group: FlyingGroup, cp: ControlPoint, at: db.StartingPosition = None): if not at: at = cp.at + position = at if isinstance(at, Point) else at.position - if isinstance(at, Point): - group.add_waypoint(at, RTB_ALTITUDE) - elif isinstance(at, Group): - group.add_waypoint(at.position, RTB_ALTITUDE) - elif issubclass(at, Airport): - group.add_waypoint(at.position, RTB_ALTITUDE) + last_waypoint = group.points[-1] + if last_waypoint is not None: + heading = position.heading_between_point(last_waypoint.position) + tod_location = position.point_from_heading(heading, RTB_DISTANCE) + self._add_radio_waypoint(group, tod_location, last_waypoint.alt) + + destination_waypoint = self._add_radio_waypoint(group, position, RTB_ALTITUDE) + if isinstance(at, Airport): group.land_at(at) + return destination_waypoint def _at_position(self, at) -> Point: if isinstance(at, Point): @@ -244,7 +256,7 @@ class AircraftConflictGenerator: orbit_task = ControlledTask(OrbitAction(ATTACK_CIRCLE_ALT, pattern=OrbitAction.OrbitPattern.Circle)) orbit_task.stop_after_duration(ATTACK_CIRCLE_DURATION * 60) - orbit_waypoint = group.add_waypoint(self.conflict.position, CAS_ALTITUDE) + orbit_waypoint = self._add_radio_waypoint(group, self.conflict.position, CAS_ALTITUDE) orbit_waypoint.tasks.append(orbit_task) orbit_waypoint.tasks.append(EngageTargets(max_distance=DEFENCE_ENGAGEMENT_MAX_DISTANCE)) @@ -263,9 +275,9 @@ class AircraftConflictGenerator: client_count=client_count, 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) + waypoint = self._add_radio_waypoint(group, self.conflict.position, CAS_ALTITUDE, WARM_START_AIRSPEED) if self.conflict.is_vector: - group.add_waypoint(self.conflict.tail, CAS_ALTITUDE, WARM_START_AIRSPEED) + self._add_radio_waypoint(group, self.conflict.tail, CAS_ALTITUDE, WARM_START_AIRSPEED) group.task = CAS.name self._setup_group(group, CAS, client_count) @@ -312,11 +324,11 @@ class AircraftConflictGenerator: location = self._group_point(self.conflict.air_defenders_location) insertion_point = self.conflict.find_insertion_point(location) - waypoint = group.add_waypoint(insertion_point, CAS_ALTITUDE, WARM_START_AIRSPEED) + waypoint = self._add_radio_waypoint(group, insertion_point, CAS_ALTITUDE, WARM_START_AIRSPEED) if self.conflict.is_vector: destination_tail = self.conflict.tail.distance_to_point(insertion_point) > self.conflict.position.distance_to_point(insertion_point) - group.add_waypoint(destination_tail and self.conflict.tail or self.conflict.position, CAS_ALTITUDE, WARM_START_AIRSPEED) + self._add_radio_waypoint(group, 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) @@ -336,7 +348,7 @@ class AircraftConflictGenerator: client_count=client_count, at=at and at or self._group_point(self.conflict.air_attackers_location)) - wayp = group.add_waypoint(self.conflict.position, CAS_ALTITUDE, WARM_START_AIRSPEED) + wayp = self._add_radio_waypoint(group, self.conflict.position, CAS_ALTITUDE, WARM_START_AIRSPEED) for target_group in target_groups: wayp.tasks.append(AttackGroup(target_group.id)) @@ -377,7 +389,7 @@ class AircraftConflictGenerator: at=at and at or self._group_point(self.conflict.air_defenders_location)) group.task = CAP.name - wayp = group.add_waypoint(self.conflict.position, CAS_ALTITUDE, WARM_START_AIRSPEED) + wayp = self._add_radio_waypoint(group, self.conflict.position, CAS_ALTITUDE, WARM_START_AIRSPEED) wayp.tasks.append(dcs.task.EngageTargets(max_distance=DEFENCE_ENGAGEMENT_MAX_DISTANCE)) wayp.tasks.append(dcs.task.OrbitAction(ATTACK_CIRCLE_ALT, pattern=OrbitAction.OrbitPattern.Circle)) self._setup_group(group, CAP, client_count) @@ -393,9 +405,9 @@ class AircraftConflictGenerator: 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) + waypoint = self._add_radio_waypoint(group, 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) + self._add_radio_waypoint(group, self.conflict.tail, WARM_START_ALTITUDE, WARM_START_AIRSPEED) group.task = CAP.name self._setup_group(group, CAP, client_count) @@ -411,14 +423,14 @@ class AircraftConflictGenerator: client_count=client_count, at=at and at or self._group_point(self.conflict.air_defenders_location)) - waypoint = group.add_waypoint(self.conflict.position, WARM_START_ALTITUDE, WARM_START_AIRSPEED) + waypoint = self._add_radio_waypoint(group, 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) + self._add_radio_waypoint(group, self.conflict.tail, WARM_START_ALTITUDE, WARM_START_AIRSPEED) else: heading = group.position.heading_between_point(self.conflict.position) - waypoint = group.add_waypoint(self.conflict.position.point_from_heading(heading, BARCAP_RACETRACK_DISTANCE), - WARM_START_ALTITUDE, - WARM_START_AIRSPEED) + waypoint = self._add_radio_waypoint(group, self.conflict.position.point_from_heading(heading, BARCAP_RACETRACK_DISTANCE), + WARM_START_ALTITUDE, + WARM_START_AIRSPEED) waypoint.tasks.append(OrbitAction(WARM_START_ALTITUDE, WARM_START_AIRSPEED)) group.task = CAP.name @@ -437,9 +449,11 @@ class AircraftConflictGenerator: client_count=client_count, at=self._group_point(self.conflict.air_defenders_location)) - waypoint = group.add_waypoint(destination.position.random_point_within(0, 0), TRANSPORT_LANDING_ALT) + waypoint = self._rtb_for(group, self.conflict.to_cp) if escort: self.escort_targets.append((group, group.points.index(waypoint))) + + self._add_radio_waypoint(group, destination.position, RTB_ALTITUDE) group.task = Transport.name group.land_at(destination) @@ -456,11 +470,11 @@ class AircraftConflictGenerator: group.task = CAP.name group.points[0].tasks.append(EngageTargets(max_distance=INTERCEPT_MAX_DISTANCE)) - wayp = group.add_waypoint(self.conflict.position, WARM_START_ALTITUDE, INTERCEPTION_AIRSPEED) + wayp = self._add_radio_waypoint(group, 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._add_radio_waypoint(group, self.conflict.tail, CAS_ALTITUDE, WARM_START_ALTITUDE) self._setup_group(group, CAP, client_count) self._rtb_for(group, self.conflict.from_cp, at) @@ -476,9 +490,5 @@ class AircraftConflictGenerator: at=at and at or self._group_point(self.conflict.air_attackers_location) ) - group.add_waypoint( - pos=self.conflict.position, - altitude=HELI_ALT, - ) - + self._add_radio_waypoint(group, self.conflict.position, HELI_ALT) self._setup_group(group, Transport, client_count) diff --git a/gen/conflictgen.py b/gen/conflictgen.py index c4d09f7e..5f3b4a21 100644 --- a/gen/conflictgen.py +++ b/gen/conflictgen.py @@ -28,7 +28,7 @@ CAP_CAS_DISTANCE = 10000, 120000 GROUND_INTERCEPT_SPREAD = 5000 GROUND_DISTANCE_FACTOR = 1 -GROUND_DISTANCE = 4000 +GROUND_DISTANCE = 2000 GROUND_ATTACK_DISTANCE = 25000, 13000 @@ -219,8 +219,8 @@ class Conflict: pos = pos.point_from_heading(heading, 500) - logging.info("Didn't find ground position!") - return None + logging.error("Didn't find ground position!") + return initial @classmethod def capture_conflict(cls, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater): @@ -305,7 +305,7 @@ class Conflict: initial_location = to_cp.position.random_point_within(*GROUND_ATTACK_DISTANCE) position = Conflict._find_ground_position(initial_location, GROUND_INTERCEPT_SPREAD, _heading_sum(heading, 180), theater) if not position: - heading = to_cp.find_radial(to_cp.position.heading_between_point(from_cp.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) return cls( diff --git a/gen/visualgen.py b/gen/visualgen.py index d0a35aab..3de00424 100644 --- a/gen/visualgen.py +++ b/gen/visualgen.py @@ -9,6 +9,7 @@ from dcs.unit import Static from theater import * from .conflictgen import * #from game.game import Game +from game import db class MarkerSmoke(unittype.StaticType): diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py new file mode 100644 index 00000000..f84c5836 --- /dev/null +++ b/tests/integration/__init__.py @@ -0,0 +1,10 @@ +from tests.integration import baseattack, convoystrike, frontlineattack, insurgentattack, intercept, navalintercept, strike + +if __name__ == "__main__": + baseattack.execute_all() + convoystrike.execute_all() + frontlineattack.execute_all() + insurgentattack.execute_all() + intercept.execute_all() + navalintercept.execute_all() + strike.execute_all() diff --git a/tests/integration/baseattack.py b/tests/integration/baseattack.py new file mode 100644 index 00000000..0b867745 --- /dev/null +++ b/tests/integration/baseattack.py @@ -0,0 +1,46 @@ +from theater.caucasus import CaucasusTheater +from theater.nevada import NevadaTheater + +from tests.integration.util import * + +PLAYER_COUNTRY = "USA" +ENEMY_COUNTRY = "Russia" + + +def execute(game, player_cp, enemy_cp, departure_cp = None): + e = BaseAttackEvent(game, player_cp, enemy_cp, enemy_cp.position, PLAYER_COUNTRY, ENEMY_COUNTRY) + + departures = [departure_cp] if departure_cp else game.theater.player_points() + for departure_cp in departures: + if e.is_departure_available_from(departure_cp): + print("{} for {} ({}) - {}".format(e, player_cp, departure_cp, enemy_cp)) + e.departure_cp = departure_cp + e.player_attacking(autoflights_for(e, PLAYER_COUNTRY)) + + e.generate() + execute_autocommit(e) + e.generate_quick() + execute_autocommit(e) + + +def execute_theater(theater_klass): + print("Theater: {}".format(theater_klass)) + game, theater = init(PLAYER_COUNTRY, ENEMY_COUNTRY, theater_klass) + + total_events = 0 + while len(theater.enemy_points()) > 0: + for player_cp, enemy_cp in theater.conflicts(): + execute(game, player_cp, enemy_cp) + + enemy_cp.captured = True + + print("Total: {}".format(total_events)) + + +def execute_all(): + for theater_klass in [CaucasusTheater, PersianGulfTheater, NevadaTheater]: + execute_theater(theater_klass) + + +if __name__ == "__main__": + execute_all() diff --git a/tests/integration/convoystrike.py b/tests/integration/convoystrike.py new file mode 100644 index 00000000..d690a38c --- /dev/null +++ b/tests/integration/convoystrike.py @@ -0,0 +1,50 @@ +from theater.caucasus import CaucasusTheater +from theater.nevada import NevadaTheater + +from tests.integration.util import * + +PLAYER_COUNTRY = "USA" +ENEMY_COUNTRY = "Russia" + + +def execute(game, player_cp, enemy_cp, departure_cp = None): + e = ConvoyStrikeEvent(game, player_cp, enemy_cp, enemy_cp.position, PLAYER_COUNTRY, ENEMY_COUNTRY) + + departures = [departure_cp] if departure_cp else game.theater.player_points() + for departure_cp in departures: + if e.is_departure_available_from(departure_cp): + enemy_cp.base.strength = 1 + for _ in range(10): + print("{} for {} ({}) - {} ({})".format(e, player_cp, departure_cp, enemy_cp, enemy_cp.base.strength)) + e.departure_cp = departure_cp + e.player_attacking(autoflights_for(e, PLAYER_COUNTRY)) + + e.generate() + execute_autocommit(e) + e.generate_quick() + execute_autocommit(e) + + enemy_cp.base.affect_strength(-0.1) + + +def execute_theater(theater_klass): + print("Theater: {}".format(theater_klass)) + game, theater = init(PLAYER_COUNTRY, ENEMY_COUNTRY, theater_klass) + + total_events = 0 + while len(theater.enemy_points()) > 0: + for player_cp, enemy_cp in theater.conflicts(): + execute(game, player_cp, enemy_cp) + + enemy_cp.captured = True + + print("Total: {}".format(total_events)) + + +def execute_all(): + for theater_klass in [CaucasusTheater, PersianGulfTheater, NevadaTheater]: + execute_theater(theater_klass) + + +if __name__ == "__main__": + execute_all() diff --git a/tests/integration/frontlineattack.py b/tests/integration/frontlineattack.py new file mode 100644 index 00000000..695a76da --- /dev/null +++ b/tests/integration/frontlineattack.py @@ -0,0 +1,52 @@ +from theater.caucasus import CaucasusTheater +from theater.nevada import NevadaTheater + +from game.event.frontlineattack import FrontlineAttackEvent + +from tests.integration.util import * + +PLAYER_COUNTRY = "USA" +ENEMY_COUNTRY = "Russia" + + +def execute(game, player_cp, enemy_cp, departure_cp = None): + e = FrontlineAttackEvent(game, player_cp, enemy_cp, enemy_cp.position, PLAYER_COUNTRY, ENEMY_COUNTRY) + + departures = [departure_cp] if departure_cp else game.theater.player_points() + for departure_cp in departures: + if e.is_departure_available_from(departure_cp): + enemy_cp.base.strength = 1 + for _ in range(10): + print("{} for {} ({}) - {} ({})".format(e, player_cp, departure_cp, enemy_cp, enemy_cp.base.strength)) + e.departure_cp = departure_cp + e.player_attacking(autoflights_for(e, PLAYER_COUNTRY)) + + e.generate() + execute_autocommit(e) + e.generate_quick() + execute_autocommit(e) + + enemy_cp.base.affect_strength(-0.1) + + +def execute_theater(theater_klass): + print("Theater: {}".format(theater_klass)) + game, theater = init(PLAYER_COUNTRY, ENEMY_COUNTRY, theater_klass) + + total_events = 0 + while len(theater.enemy_points()) > 0: + for player_cp, enemy_cp in theater.conflicts(): + execute(game, player_cp, enemy_cp) + + enemy_cp.captured = True + + print("Total: {}".format(total_events)) + + +def execute_all(): + for theater_klass in [CaucasusTheater, PersianGulfTheater, NevadaTheater]: + execute_theater(theater_klass) + + +if __name__ == "__main__": + execute_all() diff --git a/tests/integration/insurgentattack.py b/tests/integration/insurgentattack.py new file mode 100644 index 00000000..b996e242 --- /dev/null +++ b/tests/integration/insurgentattack.py @@ -0,0 +1,48 @@ +from theater.caucasus import CaucasusTheater +from theater.nevada import NevadaTheater + +from game.event.insurgentattack import InsurgentAttackEvent + +from tests.integration.util import * + +PLAYER_COUNTRY = "USA" +ENEMY_COUNTRY = "Russia" + + +def execute(game, player_cp, enemy_cp, departure_cp = None): + e = InsurgentAttackEvent(game, enemy_cp, player_cp, player_cp.position, ENEMY_COUNTRY, PLAYER_COUNTRY) + + departures = [departure_cp] if departure_cp else game.theater.player_points() + for departure_cp in departures: + if e.is_departure_available_from(departure_cp): + print("{} for {} ({}) - {} ({})".format(e, player_cp, departure_cp, enemy_cp, enemy_cp.base.strength)) + e.departure_cp = departure_cp + e.player_defending(autoflights_for(e, PLAYER_COUNTRY)) + + e.generate() + execute_autocommit(e) + e.generate_quick() + execute_autocommit(e) + + +def execute_theater(theater_klass): + print("Theater: {}".format(theater_klass)) + game, theater = init(PLAYER_COUNTRY, ENEMY_COUNTRY, theater_klass) + + total_events = 0 + while len(theater.enemy_points()) > 0: + for player_cp, enemy_cp in theater.conflicts(): + execute(game, player_cp, enemy_cp) + + enemy_cp.captured = True + + print("Total: {}".format(total_events)) + + +def execute_all(): + for theater_klass in [CaucasusTheater, PersianGulfTheater, NevadaTheater]: + execute_theater(theater_klass) + + +if __name__ == "__main__": + execute_all() diff --git a/tests/integration/intercept.py b/tests/integration/intercept.py new file mode 100644 index 00000000..933c7a7c --- /dev/null +++ b/tests/integration/intercept.py @@ -0,0 +1,48 @@ +from theater.caucasus import CaucasusTheater +from theater.nevada import NevadaTheater + +from game.event.intercept import InterceptEvent + +from tests.integration.util import * + +PLAYER_COUNTRY = "USA" +ENEMY_COUNTRY = "Russia" + + +def execute(game, player_cp, enemy_cp, departure_cp = None): + e = InterceptEvent(game, player_cp, enemy_cp, enemy_cp.position, PLAYER_COUNTRY, ENEMY_COUNTRY) + + departures = [departure_cp] if departure_cp else game.theater.player_points() + for departure_cp in departures: + if e.is_departure_available_from(departure_cp): + print("{} for {} ({}) - {} ({})".format(e, player_cp, departure_cp, enemy_cp, enemy_cp.base.strength)) + e.departure_cp = departure_cp + e.player_attacking(autoflights_for(e, PLAYER_COUNTRY)) + + e.generate() + execute_autocommit(e) + e.generate_quick() + execute_autocommit(e) + + +def execute_theater(theater_klass): + print("Theater: {}".format(theater_klass)) + game, theater = init(PLAYER_COUNTRY, ENEMY_COUNTRY, theater_klass) + + total_events = 0 + while len(theater.enemy_points()) > 0: + for player_cp, enemy_cp in theater.conflicts(): + execute(game, player_cp, enemy_cp) + + enemy_cp.captured = True + + print("Total: {}".format(total_events)) + + +def execute_all(): + for theater_klass in [CaucasusTheater, PersianGulfTheater, NevadaTheater]: + execute_theater(theater_klass) + + +if __name__ == "__main__": + execute_all() diff --git a/tests/integration/navalintercept.py b/tests/integration/navalintercept.py new file mode 100644 index 00000000..e2aef1a9 --- /dev/null +++ b/tests/integration/navalintercept.py @@ -0,0 +1,49 @@ +from theater.caucasus import CaucasusTheater +from theater.nevada import NevadaTheater + +from game.event.intercept import InterceptEvent + +from tests.integration.util import * + +PLAYER_COUNTRY = "USA" +ENEMY_COUNTRY = "Russia" + + +def execute(game, player_cp, enemy_cp, departure_cp = None): + e = NavalInterceptEvent(game, player_cp, enemy_cp, enemy_cp.position, PLAYER_COUNTRY, ENEMY_COUNTRY) + + departures = [departure_cp] if departure_cp else game.theater.player_points() + for departure_cp in departures: + if e.is_departure_available_from(departure_cp): + print("{} for {} ({}) - {} ({})".format(e, player_cp, departure_cp, enemy_cp, enemy_cp.base.strength)) + e.departure_cp = departure_cp + e.player_attacking(autoflights_for(e, PLAYER_COUNTRY)) + + e.generate() + execute_autocommit(e) + e.generate_quick() + execute_autocommit(e) + + +def execute_theater(theater_klass): + print("Theater: {}".format(theater_klass)) + game, theater = init(PLAYER_COUNTRY, ENEMY_COUNTRY, theater_klass) + + total_events = 0 + while len(theater.enemy_points()) > 0: + for player_cp, enemy_cp in theater.conflicts(): + if enemy_cp.radials != LAND: + execute(game, player_cp, enemy_cp) + + enemy_cp.captured = True + + print("Total: {}".format(total_events)) + + +def execute_all(): + for theater_klass in [CaucasusTheater, PersianGulfTheater, NevadaTheater]: + execute_theater(theater_klass) + + +if __name__ == "__main__": + execute_all() diff --git a/tests/integration/strike.py b/tests/integration/strike.py new file mode 100644 index 00000000..7c6fa190 --- /dev/null +++ b/tests/integration/strike.py @@ -0,0 +1,48 @@ +from theater.caucasus import CaucasusTheater +from theater.nevada import NevadaTheater + +from game.event.intercept import InterceptEvent + +from tests.integration.util import * + +PLAYER_COUNTRY = "USA" +ENEMY_COUNTRY = "Russia" + + +def execute(game, player_cp, enemy_cp, departure_cp = None): + e = StrikeEvent(game, player_cp, enemy_cp, enemy_cp.position, PLAYER_COUNTRY, ENEMY_COUNTRY) + + departures = [departure_cp] if departure_cp else game.theater.player_points() + for departure_cp in departures: + if e.is_departure_available_from(departure_cp): + print("{} for {} ({}) - {} ({})".format(e, player_cp, departure_cp, enemy_cp, enemy_cp.base.strength)) + e.departure_cp = departure_cp + e.player_attacking(autoflights_for(e, PLAYER_COUNTRY)) + + e.generate() + execute_autocommit(e) + e.generate_quick() + execute_autocommit(e) + + +def execute_theater(theater_klass): + print("Theater: {}".format(theater_klass)) + game, theater = init(PLAYER_COUNTRY, ENEMY_COUNTRY, theater_klass) + + total_events = 0 + while len(theater.enemy_points()) > 0: + for player_cp, enemy_cp in theater.conflicts(): + execute(game, player_cp, enemy_cp) + + enemy_cp.captured = True + + print("Total: {}".format(total_events)) + + +def execute_all(): + for theater_klass in [CaucasusTheater, PersianGulfTheater, NevadaTheater]: + execute_theater(theater_klass) + + +if __name__ == "__main__": + execute_all() diff --git a/tests/integration/util.py b/tests/integration/util.py new file mode 100644 index 00000000..46310e43 --- /dev/null +++ b/tests/integration/util.py @@ -0,0 +1,80 @@ +from dcs.mission import Mission + +from game import * +from game.event import * +from game.db import * + +from theater.persiangulf import * +from theater import start_generator + +PLAYER_COUNTRY = None +ENEMY_COUNTRY = None + + +def init(player_country: str, enemy_country: str, theater_klass: typing.Type[ConflictTheater]) -> typing.Tuple[Game, ConflictTheater]: + global PLAYER_COUNTRY + global ENEMY_COUNTRY + + PLAYER_COUNTRY = player_country + ENEMY_COUNTRY = enemy_country + + # prerequisites + persistency.setup("./tests/userfolder/") + theater = theater_klass() + start_generator.generate_inital_units(theater, ENEMY_COUNTRY, True, 1) + start_generator.generate_groundobjects(theater) + return Game(PLAYER_COUNTRY, ENEMY_COUNTRY, theater), theater + + +def autoflights_for(event: Event, country: str) -> TaskForceDict: + result = {} + for task in event.tasks: + result[task] = {find_unittype(task, country)[0]: (1, 1)} + return result + + +class AutodebriefType(Enum): + EVERYONE_DEAD = 0 + PLAYER_DEAD = 1 + ENEMY_DEAD = 2 + + +def autodebrief_for(event: Event, type: AutodebriefType) -> Debriefing: + mission = event.operation.current_mission # type: Mission + + countries = [] + if type == AutodebriefType.PLAYER_DEAD or type == AutodebriefType.EVERYONE_DEAD: + countries.append(mission.country(PLAYER_COUNTRY)) + + if type == AutodebriefType.ENEMY_DEAD or type == AutodebriefType.EVERYONE_DEAD: + countries.append(mission.country(ENEMY_COUNTRY)) + + dead_units = [] + for country in countries: + for group in country.plane_group + country.vehicle_group + country.helicopter_group: + for unit in group.units: + dead_units.append(str(unit.name)) + + return Debriefing(dead_units, []) + + +def event_state_save(e: Event) -> typing.Tuple[Base, Base]: + return (copy.deepcopy(e.from_cp.base), copy.deepcopy(e.to_cp.base)) + + +def event_state_restore(e: Event, state: typing.Tuple[Base, Base]): + e.from_cp.base, e.to_cp.base = state[0], state[1] + + +def execute_autocommit(e: Event): + state = event_state_save(e) + e.commit(autodebrief_for(e, AutodebriefType.EVERYONE_DEAD)) + event_state_restore(e, state) + + state = event_state_save(e) + e.commit(autodebrief_for(e, AutodebriefType.PLAYER_DEAD)) + event_state_restore(e, state) + + state = event_state_save(e) + e.commit(autodebrief_for(e, AutodebriefType.ENEMY_DEAD)) + event_state_restore(e, state) diff --git a/tests/userfolder/DCS/Missions/empty.txt b/tests/userfolder/DCS/Missions/empty.txt new file mode 100644 index 00000000..e69de29b diff --git a/theater/base.py b/theater/base.py index c62c1474..5f3b99cb 100644 --- a/theater/base.py +++ b/theater/base.py @@ -56,7 +56,7 @@ class Base: def _find_best_unit(self, dict, for_type: Task, count: int) -> typing.Dict: if count <= 0: - logging.info("{}: no units for {}".format(self, for_type)) + logging.warning("{}: no units for {}".format(self, for_type)) return {} sorted_units = [key for key in dict.keys() if key in db.UNIT_BY_TASK[for_type]] diff --git a/ui/overviewcanvas.py b/ui/overviewcanvas.py index 0d545fd0..129365eb 100644 --- a/ui/overviewcanvas.py +++ b/ui/overviewcanvas.py @@ -10,30 +10,29 @@ from ui.styles import STYLES from ui.window import * -EVENT_DEPARTURE_MAX_DISTANCE = 340000 - EVENT_COLOR_ATTACK = (100, 100, 255) EVENT_COLOR_DEFENSE = (255, 100, 100) +RED = (255, 125, 125) +BRIGHT_RED = (200, 64, 64) +BLUE = (164, 164, 255) +DARK_BLUE = (45, 62, 80) +WHITE = (255, 255, 255) +GREEN = (128, 186, 128) +BRIGHT_GREEN = (64, 200, 64) +BLACK = (0, 0, 0) + +BACKGROUND = pygame.Color(0, 64, 64) +ANTIALIASING = True + +WIDTH = 1066 +HEIGHT = 600 +MAP_PADDING = 100 + class OverviewCanvas: mainmenu = None # type: ui.mainmenu.MainMenu - - BRIGHT_RED = (200, 64, 64) - BRIGHT_GREEN = (64, 200, 64) - - RED = (255, 125, 125) - BLUE = (164, 164, 255) - DARK_BLUE = (45, 62, 80) - WHITE = (255, 255, 255) - GREEN = (128, 186, 128) - BLACK = (0, 0, 0) - BACKGROUND = pygame.Color(0, 64, 64) - ANTIALIASING = True - - WIDTH = 1066 - HEIGHT = 600 - MAP_PADDING = 100 + budget_label = None # type: Label started = None ground_assets_icons = None # type: typing.Dict[str, pygame.Surface] @@ -84,29 +83,27 @@ class OverviewCanvas: self.wrapper.grid(column=0, row=0, sticky=NSEW) # Adds grid self.wrapper.pack(side=LEFT) # packs window to the left - self.embed = Frame(self.wrapper, width=self.WIDTH, height=self.HEIGHT, borderwidth=2, **STYLES["frame-wrapper"]) - self.embed.grid(column=0, row=0, sticky=NSEW) # Adds grid + self.embed = Frame(self.wrapper, width=WIDTH, height=HEIGHT, borderwidth=2, **STYLES["frame-wrapper"]) + self.embed.grid(column=0, row=1, sticky=NSEW) # Adds grid self.options = Frame(self.wrapper, borderwidth=2, **STYLES["frame-wrapper"]) - self.options.grid(column=0, row=1, sticky=NSEW) + self.options.grid(column=0, row=0, sticky=NSEW) + self.options.grid_columnconfigure(1, weight=1) self.build_map_options_panel() self.init_sdl_layer() self.init_sdl_thread() def build_map_options_panel(self): - def force_redraw(): - if self.screen: - self.redraw_required = True - self.draw() col = 0 - Button(self.options, text="Configuration", command=None, **STYLES["btn-primary"]).grid(column=col, row=0, sticky=NE) + Button(self.options, text="Configuration", command=self.parent.configuration_menu, **STYLES["btn-primary"]).grid(column=col, row=0, sticky=NE) col += 1 - Label(self.options, text="Budget: {}m (+{}m)".format(self.game.budget, self.game.budget_reward_amount), **STYLES["widget"]).grid(column=col, row=0, sticky=N+EW) + self.budget_label = Label(self.options, text="Budget: {}m (+{}m)".format(self.game.budget, self.game.budget_reward_amount), **STYLES["widget"]) + self.budget_label.grid(column=col, row=0, sticky=N+EW) col += 1 - Button(self.options, text="Pass turn", command=None, **STYLES["btn-primary"]).grid(column=col, row=0, sticky=NE) + Button(self.options, text="Pass turn", command=self.parent.pass_turn, **STYLES["btn-primary"]).grid(column=col, row=0, sticky=NW) col += 1 def map_size_toggle(self): @@ -115,8 +112,8 @@ class OverviewCanvas: self.options.configure(width=0) self.expanded = False else: - self.embed.configure(width=self.WIDTH) - self.options.configure(width=self.WIDTH) + self.embed.configure(width=WIDTH) + self.options.configure(width=WIDTH) self.expanded = True def on_close(self): @@ -131,8 +128,8 @@ class OverviewCanvas: os.environ['SDL_VIDEODRIVER'] = 'windib' # Create pygame 'screen' - self.screen = pygame.display.set_mode((self.WIDTH, self.HEIGHT), pygame.DOUBLEBUF | pygame.HWSURFACE) - self.screen.fill(pygame.Color(*self.BLACK)) + self.screen = pygame.display.set_mode((WIDTH, HEIGHT), pygame.DOUBLEBUF | pygame.HWSURFACE) + self.screen.fill(pygame.Color(*BLACK)) # Load icons resources self.ground_assets_icons = {} @@ -157,14 +154,14 @@ class OverviewCanvas: # Load the map image self.map = pygame.image.load(os.path.join("resources", self.game.theater.overview_image)).convert() - pygame.draw.rect(self.map, self.BLACK, (0, 0, self.map.get_width(), self.map.get_height()), 10) - pygame.draw.rect(self.map, self.WHITE, (0, 0, self.map.get_width(), self.map.get_height()), 5) + pygame.draw.rect(self.map, BLACK, (0, 0, self.map.get_width(), self.map.get_height()), 10) + pygame.draw.rect(self.map, WHITE, (0, 0, self.map.get_width(), self.map.get_height()), 5) # Create surfaces for drawing - self.surface = pygame.Surface((self.map.get_width() + self.MAP_PADDING * 2, - self.map.get_height() + self.MAP_PADDING * 2)) + self.surface = pygame.Surface((self.map.get_width() + MAP_PADDING * 2, + self.map.get_height() + MAP_PADDING * 2)) self.surface.set_alpha(None) - self.overlay = pygame.Surface((self.WIDTH, self.HEIGHT), pygame.SRCALPHA) + self.overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA) # Init pygame display pygame.display.init() @@ -210,6 +207,10 @@ class OverviewCanvas: if event.type == pygame.MOUSEMOTION: self.redraw_required = True elif event.type == pygame.MOUSEBUTTONDOWN: + """ + Due to rendering not really supporting the zoom this is currently disabled. + @TODO: improve rendering so zoom would actually make sense + # Scroll wheel if event.button == 4: self.zoom += 0.25 @@ -217,6 +218,7 @@ class OverviewCanvas: elif event.button == 5: self.zoom -= 0.25 self.redraw_required = True + """ if event.button == 3: right_down = True @@ -239,8 +241,8 @@ class OverviewCanvas: if self.redraw_required: # Fill - self.screen.fill(self.BACKGROUND) - self.surface.fill(self.BACKGROUND) + self.screen.fill(BACKGROUND) + self.surface.fill(BACKGROUND) self.overlay.fill(pygame.Color(0, 0, 0, 0)) # Surface @@ -260,10 +262,10 @@ class OverviewCanvas: self.redraw_required = False def draw_map(self, surface: pygame.Surface, overlay: pygame.Surface, mouse_pos: (int, int), mouse_down: [bool, bool]): - self.surface.blit(self.map, (self.MAP_PADDING, self.MAP_PADDING)) + self.surface.blit(self.map, (MAP_PADDING, MAP_PADDING)) # Display zoom level on overlay - zoom_lvl = self.font.render(" x " + str(self.zoom) + " ", self.ANTIALIASING, self.WHITE, self.DARK_BLUE) + zoom_lvl = self.font.render(" x " + str(self.zoom) + " ", ANTIALIASING, WHITE, DARK_BLUE) self.overlay.blit(zoom_lvl, (self.overlay.get_width()-zoom_lvl.get_width()-5, self.overlay.get_height()-zoom_lvl.get_height()-5)) @@ -281,7 +283,7 @@ class OverviewCanvas: elif connected_cp.captured and cp.captured: color = self._player_color() else: - color = self.BLACK + color = BLACK pygame.draw.line(surface, color, coords, connected_coords, 2) @@ -325,12 +327,12 @@ class OverviewCanvas: else: color = self._enemy_color() - pygame.draw.circle(self.surface, self.BLACK, (int(coords[0]), int(coords[1])), int(radius)) + pygame.draw.circle(self.surface, BLACK, (int(coords[0]), int(coords[1])), int(radius)) pygame.draw.circle(self.surface, color, (int(coords[0]), int(coords[1])), int(radius_m)) - label = self.font.render(cp.name, self.ANTIALIASING, (225, 225, 225), self.BLACK) - labelHover = self.font.render(cp.name, self.ANTIALIASING, (255, 255, 255), (128, 186, 128)) - labelClick = self.font.render(cp.name, self.ANTIALIASING, (255, 255, 255), (122, 122, 255)) + label = self.font.render(cp.name, ANTIALIASING, (225, 225, 225), BLACK) + labelHover = self.font.render(cp.name, ANTIALIASING, (255, 255, 255), (128, 186, 128)) + labelClick = self.font.render(cp.name, ANTIALIASING, (255, 255, 255), (122, 122, 255)) point = coords[0] - label.get_width() / 2 + 1, coords[1] + 1 rect = pygame.Rect(*point, label.get_width(), label.get_height()) @@ -346,56 +348,56 @@ class OverviewCanvas: self.draw_base_info(self.overlay, cp, (0, 0)) if self.selected_event_info: if self._cp_available_for_selected_event(cp): - pygame.draw.line(self.surface, self.WHITE, rect.center, self.selected_event_info[1]) + pygame.draw.line(self.surface, WHITE, rect.center, self.selected_event_info[1]) else: self.surface.blit(label, (coords[0] - label.get_width() / 2 + 1, coords[1] + 1)) if self.display_forces.get(): units_title = " {} / {} / {} ".format(cp.base.total_planes, cp.base.total_armor, cp.base.total_aa) - label2 = self.fontsmall.render(units_title, self.ANTIALIASING, color, (30, 30, 30)) + label2 = self.fontsmall.render(units_title, ANTIALIASING, color, (30, 30, 30)) self.surface.blit(label2, (coords[0] - label2.get_width() / 2, coords[1] + label.get_height() + 1)) return mouse_down def draw_base_info(self, surface: pygame.Surface, control_point: ControlPoint, pos): - title = self.font.render(control_point.name, self.ANTIALIASING, self.BLACK, self.GREEN) - hp = self.font.render("Strength : ", self.ANTIALIASING, (225, 225, 225), self.BLACK) + title = self.font.render(control_point.name, ANTIALIASING, BLACK, GREEN) + hp = self.font.render("Strength : ", ANTIALIASING, (225, 225, 225), BLACK) armor_txt = "ARMOR > " for key, value in control_point.base.armor.items(): armor_txt += key.id + " x " + str(value) + " | " - armor = self.font.render(armor_txt, self.ANTIALIASING, (225, 225, 225), self.BLACK) + armor = self.font.render(armor_txt, ANTIALIASING, (225, 225, 225), BLACK) aircraft_txt = "AIRCRAFT > " for key, value in control_point.base.aircraft.items(): aircraft_txt += key.id + " x " + str(value) + " | " - aircraft = self.font.render(aircraft_txt, self.ANTIALIASING, (225, 225, 225), self.BLACK) + aircraft = self.font.render(aircraft_txt, ANTIALIASING, (225, 225, 225), BLACK) aa_txt = "AA/SAM > " for key, value in control_point.base.aa.items(): aa_txt += key.id + " x " + str(value) + " | " - aa = self.font.render(aa_txt, self.ANTIALIASING, (225, 225, 225), self.BLACK) + aa = self.font.render(aa_txt, ANTIALIASING, (225, 225, 225), BLACK) lineheight = title.get_height() w = max([max([a.get_width() for a in [title, armor, aircraft, aa]]), 150]) h = 5 * lineheight + 4 * 5 # Draw frame - pygame.draw.rect(surface, self.GREEN, (pos[0], pos[1], w + 8, h + 8)) - pygame.draw.rect(surface, self.BLACK, (pos[0] + 2, pos[1] + 2, w + 4, h + 4)) - pygame.draw.rect(surface, self.GREEN, (pos[0] + 2, pos[1], w + 4, lineheight + 4)) + pygame.draw.rect(surface, GREEN, (pos[0], pos[1], w + 8, h + 8)) + pygame.draw.rect(surface, BLACK, (pos[0] + 2, pos[1] + 2, w + 4, h + 4)) + pygame.draw.rect(surface, GREEN, (pos[0] + 2, pos[1], w + 4, lineheight + 4)) # Title surface.blit(title, (pos[0] + 4, 4 + pos[1])) surface.blit(hp, (pos[0] + 4, 4 + pos[1] + lineheight + 5)) # Draw gauge - pygame.draw.rect(surface, self.WHITE, + pygame.draw.rect(surface, WHITE, (pos[0] + hp.get_width() + 3, 4 + pos[1] + lineheight + 5, 54, lineheight)) - pygame.draw.rect(surface, self.BRIGHT_RED, + pygame.draw.rect(surface, BRIGHT_RED, (pos[0] + hp.get_width() + 5, 4 + pos[1] + lineheight + 5 + 2, 50, lineheight - 4)) - pygame.draw.rect(surface, self.BRIGHT_GREEN, ( + pygame.draw.rect(surface, BRIGHT_GREEN, ( pos[0] + hp.get_width() + 5, 4 + pos[1] + lineheight + 5 + 2, 50 * control_point.base.strength, lineheight - 4)) # Text @@ -405,8 +407,8 @@ class OverviewCanvas: def draw_selected_event_info(self): event = self.selected_event_info[0] - title = self.font.render(str(event), self.ANTIALIASING, self.BLACK, self.GREEN) - hint = self.font.render("Select CP to depart from.", self.ANTIALIASING, (225, 225, 225), self.BLACK) + title = self.font.render(str(event), ANTIALIASING, BLACK, GREEN) + hint = self.font.render("Select CP to depart from.", ANTIALIASING, (225, 225, 225), BLACK) w = hint.get_width() h = title.get_height() + hint.get_height() + 20 @@ -414,9 +416,9 @@ class OverviewCanvas: pos = self.overlay.get_width() / 2 - w / 2, self.overlay.get_height() - h # Draw frame - pygame.draw.rect(self.overlay, self.GREEN, (pos[0], pos[1], w + 8, h + 8)) - pygame.draw.rect(self.overlay, self.BLACK, (pos[0] + 2, pos[1] + 2, w + 4, h + 4)) - pygame.draw.rect(self.overlay, self.GREEN, (pos[0] + 2, pos[1], w + 4, title.get_height() + 4)) + pygame.draw.rect(self.overlay, GREEN, (pos[0], pos[1], w + 8, h + 8)) + pygame.draw.rect(self.overlay, BLACK, (pos[0] + 2, pos[1] + 2, w + 4, h + 4)) + pygame.draw.rect(self.overlay, GREEN, (pos[0] + 2, pos[1], w + 4, title.get_height() + 4)) # Title self.overlay.blit(title, (pos[0] + 4, 4 + pos[1])) @@ -444,7 +446,7 @@ class OverviewCanvas: self.draw_ground_object_info(ground_object, (x, y), color, surface) def draw_ground_object_info(self, ground_object: TheaterGroundObject, pos, color, surface: pygame.Surface): - lb = self.font.render(str(ground_object), self.ANTIALIASING, color, self.BLACK) + lb = self.font.render(str(ground_object), ANTIALIASING, color, BLACK) surface.blit(lb, (pos[0] + 18, pos[1])) def draw_events(self, surface: pygame.Surface, mouse_pos, mouse_down): @@ -500,7 +502,7 @@ class OverviewCanvas: if rect.collidepoint(*mouse_pos) or self.selected_event_info == (event, rect.center): if not label_to_draw: - label_to_draw = self.font.render(str(event), self.ANTIALIASING, self.WHITE, self.BLACK), rect.center + label_to_draw = self.font.render(str(event), ANTIALIASING, WHITE, BLACK), rect.center if rect.collidepoint(*mouse_pos): if mouse_down[0]: @@ -517,7 +519,7 @@ class OverviewCanvas: def _selected_cp(self, cp): if self.selected_event_info: - if self._cp_available_for_selected_event(cp): + if self. _cp_available_for_selected_event(cp): event = self.selected_event_info[0] event.departure_cp = cp @@ -551,8 +553,8 @@ class OverviewCanvas: X = point_b_img[1] + X_offset * X_scale Y = point_a_img[0] - Y_offset * Y_scale - X += self.MAP_PADDING - Y += self.MAP_PADDING + X += MAP_PADDING + Y += MAP_PADDING return X > treshold and X or treshold, Y > treshold and Y or treshold @@ -575,27 +577,18 @@ class OverviewCanvas: def _cp_available_for_selected_event(self, cp: ControlPoint) -> bool: event = self.selected_event_info[0] - - if not cp.captured: - return False - - if event.location.distance_to_point(cp.position) > EVENT_DEPARTURE_MAX_DISTANCE: - return False - - if cp.is_global and not event.global_cp_available: - return False - - return True + return event.is_departure_available_from(cp) def _player_color(self): - return self.game.player == "USA" and self.BLUE or self.RED + return self.game.player == "USA" and BLUE or RED def _enemy_color(self): - return self.game.player == "USA" and self.RED or self.BLUE + return self.game.player == "USA" and RED or BLUE def update(self): self.redraw_required = True self.draw() + self.budget_label.text = "Budget: {}m (+{}m)".format(self.game.budget, self.game.budget_reward_amount) def compute_display_rules(self): return sum([1 if a.get() else 0 for a in [self.display_forces, self.display_road, self.display_bases, self.display_ground_targets]]) diff --git a/userdata/debriefing.py b/userdata/debriefing.py index dc43cf47..fe95f05a 100644 --- a/userdata/debriefing.py +++ b/userdata/debriefing.py @@ -80,7 +80,7 @@ class Debriefing: nonlocal dead_units object_mission_id = int(object_mission_id_str) if object_mission_id in dead_units: - logging.info("debriefing: failed to append_dead_object {}: already exists!".format(object_mission_id)) + logging.error("debriefing: failed to append_dead_object {}: already exists!".format(object_mission_id)) return dead_units.append(object_mission_id)