integration tests for operation generation; adjusted waypoint altitude for AI to not fly too low; removed C101 from list of generated AI aircrafts

This commit is contained in:
Vasyl Horbachenko 2019-01-16 01:07:24 +02:00
parent fbbe56f954
commit f7e2c8921c
26 changed files with 653 additions and 160 deletions

View File

@ -39,7 +39,6 @@ and prioritization for the enemy (i.e. less important bases will receive units w
""" """
PRICES = { PRICES = {
# fighter # fighter
C_101CC: 8,
MiG_23MLD: 18, MiG_23MLD: 18,
Su_27: 20, Su_27: 20,
Su_33: 22, Su_33: 22,
@ -85,14 +84,14 @@ PRICES = {
C_130: 8, C_130: 8,
# armor # armor
Armor.APC_BTR_80: 12, Armor.APC_BTR_80: 16,
Armor.MBT_T_55: 14, Armor.MBT_T_55: 22,
Armor.MBT_T_80U: 18, Armor.MBT_T_80U: 28,
Armor.MBT_T_90: 20, Armor.MBT_T_90: 35,
Armor.ATGM_M1134_Stryker: 12, Armor.ATGM_M1134_Stryker: 18,
Armor.MBT_M60A3_Patton: 14, Armor.MBT_M60A3_Patton: 24,
Armor.MBT_M1A2_Abrams: 18, Armor.MBT_M1A2_Abrams: 35,
Unarmed.Transport_UAZ_469: 3, Unarmed.Transport_UAZ_469: 3,
Unarmed.Transport_Ural_375: 3, Unarmed.Transport_Ural_375: 3,
@ -137,7 +136,6 @@ Following tasks are present:
""" """
UNIT_BY_TASK = { UNIT_BY_TASK = {
CAP: [ CAP: [
C_101CC,
F_5E_3, F_5E_3,
MiG_23MLD, MiG_23MLD,
Su_27, 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 = { UNIT_BY_COUNTRY = {
"Russia": [ "Russia": [
C_101CC,
AJS37, AJS37,
MiG_23MLD, MiG_23MLD,
F_5E_3, F_5E_3,

View File

@ -25,8 +25,8 @@ class BaseAttackEvent(Event):
return "Ground attack" return "Ground attack"
def is_successfull(self, debriefing: Debriefing): 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_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[self.defender_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 attackers_success = alive_attackers >= alive_defenders
if self.departure_cp.captured: if self.departure_cp.captured:
return attackers_success return attackers_success

View File

@ -53,7 +53,7 @@ class ConvoyStrikeEvent(Event):
self.from_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) self.from_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
def is_successfull(self, debriefing: Debriefing): 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()) all_units = sum(self.targets.values())
attackers_success = (float(killed_units) / (all_units + 0.01)) > self.SUCCESS_FACTOR attackers_success = (float(killed_units) / (all_units + 0.01)) > self.SUCCESS_FACTOR
if self.from_cp.captured: if self.from_cp.captured:

View File

@ -16,6 +16,7 @@ from userdata.debriefing import Debriefing
from userdata import persistency from userdata import persistency
DIFFICULTY_LOG_BASE = 1.1 DIFFICULTY_LOG_BASE = 1.1
EVENT_DEPARTURE_MAX_DISTANCE = 340000
class Event: class Event:
@ -74,6 +75,18 @@ class Event:
def global_cp_available(self) -> bool: def global_cp_available(self) -> bool:
return False 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: def bonus(self) -> int:
return int(math.log(self.to_cp.importance + 1, DIFFICULTY_LOG_BASE) * self.BONUS_BASE) return int(math.log(self.to_cp.importance + 1, DIFFICULTY_LOG_BASE) * self.BONUS_BASE)

View File

@ -18,7 +18,7 @@ class FrontlineAttackEvent(Event):
@property @property
def tasks(self) -> typing.Collection[typing.Type[Task]]: def tasks(self) -> typing.Collection[typing.Type[Task]]:
if self.is_player_attacking: if self.is_player_attacking:
return [CAS] return [CAS, CAP]
else: else:
return [CAP] return [CAP]
@ -38,8 +38,8 @@ class FrontlineAttackEvent(Event):
return "Frontline attack" return "Frontline attack"
def is_successfull(self, debriefing: Debriefing): 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_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[self.defender_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 attackers_success = (float(alive_attackers) / (alive_defenders + 0.01)) > self.SUCCESS_FACTOR
if self.from_cp.captured: if self.from_cp.captured:
return attackers_success return attackers_success
@ -65,8 +65,7 @@ class FrontlineAttackEvent(Event):
self.to_cp.base.affect_strength(-0.1) self.to_cp.base.affect_strength(-0.1)
def player_attacking(self, flights: db.TaskForceDict): 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, op = FrontlineAttackOperation(game=self.game,
attacker_name=self.attacker_name, attacker_name=self.attacker_name,
@ -76,10 +75,36 @@ class FrontlineAttackEvent(Event):
to_cp=self.to_cp) to_cp=self.to_cp)
defenders = self.to_cp.base.assemble_attack() defenders = self.to_cp.base.assemble_attack()
attackers = db.unitdict_restrict_count(self.from_cp.base.assemble_attack(), sum(defenders.values())) max_attackers = int(math.ceil(sum(defenders.values()) * self.ATTACKER_DEFENDER_FACTOR))
op.setup(target=defenders, attackers = db.unitdict_restrict_count(self.from_cp.base.assemble_attack(), max_attackers)
op.setup(defenders=defenders,
attackers=attackers, 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 self.operation = op

View File

@ -42,8 +42,8 @@ class InterceptEvent(Event):
return True return True
def is_successfull(self, debriefing: Debriefing): def is_successfull(self, debriefing: Debriefing):
units_destroyed = debriefing.destroyed_units[self.defender_name].get(self.transport_unit, 0) units_destroyed = debriefing.destroyed_units.get(self.defender_name, {}).get(self.transport_unit, 0)
if self.departure_cp.captured: if self.from_cp.captured:
return units_destroyed > 0 return units_destroyed > 0
else: else:
return units_destroyed == 0 return units_destroyed == 0
@ -56,11 +56,11 @@ class InterceptEvent(Event):
for _, cp in self.game.theater.conflicts(True): for _, cp in self.game.theater.conflicts(True):
cp.base.affect_strength(-self.STRENGTH_INFLUENCE) cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
else: else:
self.departure_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) self.from_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
else: else:
# enemy attacking # enemy attacking
if self.is_successfull(debriefing): if self.is_successfull(debriefing):
self.departure_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) self.from_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
else: else:
self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE) self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
@ -95,7 +95,7 @@ class InterceptEvent(Event):
def player_defending(self, flights: db.TaskForceDict): def player_defending(self, flights: db.TaskForceDict):
assert CAP in flights and len(flights) == 1, "Invalid flights" 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)) self.transport_unit = random.choice(db.find_unittype(Transport, self.defender_name))
assert self.transport_unit is not None assert self.transport_unit is not None
@ -107,7 +107,8 @@ class InterceptEvent(Event):
departure_cp=self.departure_cp, departure_cp=self.departure_cp,
to_cp=self.to_cp) to_cp=self.to_cp)
op.setup(escort=flights[CAP], op.setup(location=self.location,
escort=flights[CAP],
transport={self.transport_unit: 1}, transport={self.transport_unit: 1},
interceptors=assigned_units_from(interceptors), interceptors=assigned_units_from(interceptors),
airdefense={}) airdefense={})

View File

@ -49,7 +49,7 @@ class NavalInterceptEvent(Event):
def is_successfull(self, debriefing: Debriefing): def is_successfull(self, debriefing: Debriefing):
total_targets = sum(self.targets.values()) total_targets = sum(self.targets.values())
destroyed_targets = 0 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: if unit in self.targets:
destroyed_targets += count destroyed_targets += count

View File

@ -43,19 +43,18 @@ Events:
* BaseAttackEvent - capture base * BaseAttackEvent - capture base
* InterceptEvent - air intercept * InterceptEvent - air intercept
* FrontlineAttackEvent - frontline attack * FrontlineAttackEvent - frontline attack
* FrontlineCAPEvent - frontline attack
* NavalInterceptEvent - naval intercept * NavalInterceptEvent - naval intercept
* StrikeEvent - strike event * StrikeEvent - strike event
* InfantryTransportEvent - helicopter infantry transport * InfantryTransportEvent - helicopter infantry transport
""" """
EVENT_PROBABILITIES = { EVENT_PROBABILITIES = {
# events always present; only for the player # events always present; only for the player
FrontlineAttackEvent: [100, 0], FrontlineAttackEvent: [100, 9],
#FrontlinePatrolEvent: [100, 0], #FrontlinePatrolEvent: [100, 0],
StrikeEvent: [100, 0], StrikeEvent: [100, 0],
# events randomly present; only for the player # events randomly present; only for the player
InfantryTransportEvent: [25, 0], #InfantryTransportEvent: [25, 0],
ConvoyStrikeEvent: [25, 0], ConvoyStrikeEvent: [25, 0],
# events conditionally present; for both enemy and player # 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)) self.events.append(event_class(self, enemy_cp, player_cp, player_cp.position, self.enemy, self.player))
def _generate_events(self): def _generate_events(self):
strikes_generated_for = set()
for player_cp, enemy_cp in self.theater.conflicts(True): for player_cp, enemy_cp in self.theater.conflicts(True):
for event_class, (player_probability, enemy_probability) in EVENT_PROBABILITIES.items(): for event_class, (player_probability, enemy_probability) in EVENT_PROBABILITIES.items():
if event_class in [FrontlineAttackEvent, FrontlinePatrolEvent, InfantryTransportEvent, ConvoyStrikeEvent]: if event_class in [FrontlineAttackEvent, FrontlinePatrolEvent, InfantryTransportEvent, ConvoyStrikeEvent]:
@ -170,8 +171,15 @@ class Game:
if not Conflict.has_frontline_between(player_cp, enemy_cp): if not Conflict.has_frontline_between(player_cp, enemy_cp):
continue 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): 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) 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): if enemy_probability == 100 or enemy_probability > 0 and self._roll(enemy_probability, enemy_cp.base.strength):
self._generate_enemy_event(event_class, player_cp, enemy_cp) self._generate_enemy_event(event_class, player_cp, enemy_cp)

View File

@ -7,16 +7,24 @@ MAX_DISTANCE_BETWEEN_GROUPS = 12000
class FrontlineAttackOperation(Operation): class FrontlineAttackOperation(Operation):
interceptors = None # type: db.AssignedUnitsDict
escort = None # type: db.AssignedUnitsDict
strikegroup = None # type: db.AssignedUnitsDict strikegroup = None # type: db.AssignedUnitsDict
attackers = None # type: db.ArmorDict attackers = None # type: db.ArmorDict
target = None # type: db.ArmorDict defenders = None # type: db.ArmorDict
def setup(self, def setup(self,
target: db.ArmorDict, defenders: db.ArmorDict,
attackers: db.ArmorDict, attackers: db.ArmorDict,
strikegroup: db.AssignedUnitsDict): strikegroup: db.AssignedUnitsDict,
escort: db.AssignedUnitsDict,
interceptors: db.AssignedUnitsDict):
self.strikegroup = strikegroup self.strikegroup = strikegroup
self.target = target self.escort = escort
self.interceptors = interceptors
self.defenders = defenders
self.attackers = attackers self.attackers = attackers
def prepare(self, terrain: Terrain, is_quick: bool): def prepare(self, terrain: Terrain, is_quick: bool):
@ -40,8 +48,10 @@ class FrontlineAttackOperation(Operation):
if self.is_player_attack: if self.is_player_attack:
self.prepare_carriers(db.unitdict_from(self.strikegroup)) 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()} 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) self.airgen.generate_cas_strikegroup(*assigned_units_split(planes_flights), at=self.attackers_starting_position)
@ -54,6 +64,10 @@ class FrontlineAttackOperation(Operation):
at=farp, at=farp,
escort=len(planes_flights) == 0) 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.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.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") self.briefinggen.append_waypoint("CAS AREA IP")

View File

@ -17,23 +17,23 @@ ESCORT_ENGAGEMENT_MAX_DIST = 100000
WORKAROUND_WAYP_DIST = 1000 WORKAROUND_WAYP_DIST = 1000
WARM_START_HELI_AIRSPEED = 120 WARM_START_HELI_AIRSPEED = 120
WARM_START_HELI_ALT = 1000 WARM_START_HELI_ALT = 500
WARM_START_ALTITUDE = 3000 WARM_START_ALTITUDE = 3000
WARM_START_AIRSPEED = 550 WARM_START_AIRSPEED = 550
INTERCEPTION_ALT = 3000
INTERCEPTION_AIRSPEED = 1000 INTERCEPTION_AIRSPEED = 1000
BARCAP_RACETRACK_DISTANCE = 20000 BARCAP_RACETRACK_DISTANCE = 20000
ATTACK_CIRCLE_ALT = 5000 ATTACK_CIRCLE_ALT = 1000
ATTACK_CIRCLE_DURATION = 15 ATTACK_CIRCLE_DURATION = 15
CAS_ALTITUDE = 1000 CAS_ALTITUDE = 800
RTB_ALTITUDE = 3000 RTB_ALTITUDE = 800
HELI_ALT = 900 RTB_DISTANCE = 5000
HELI_ALT = 500
TRANSPORT_LANDING_ALT = 1000 TRANSPORT_LANDING_ALT = 2000
DEFENCE_ENGAGEMENT_MAX_DISTANCE = 60000 DEFENCE_ENGAGEMENT_MAX_DISTANCE = 60000
INTERCEPT_MAX_DISTANCE = 200000 INTERCEPT_MAX_DISTANCE = 200000
@ -149,7 +149,7 @@ class AircraftConflictGenerator:
pos = Point(at.x + random.randint(100, 1000), at.y + random.randint(100, 1000)) 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)) 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, country=side,
name=name, name=name,
aircraft_type=unit_type, aircraft_type=unit_type,
@ -161,6 +161,9 @@ class AircraftConflictGenerator:
start_type=self._start_type(), start_type=self._start_type(),
group_size=count) 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: 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 count > 0
assert unit is not None assert unit is not None
@ -197,17 +200,26 @@ class AircraftConflictGenerator:
else: else:
assert False 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): def _rtb_for(self, group: FlyingGroup, cp: ControlPoint, at: db.StartingPosition = None):
if not at: if not at:
at = cp.at at = cp.at
position = at if isinstance(at, Point) else at.position
if isinstance(at, Point): last_waypoint = group.points[-1]
group.add_waypoint(at, RTB_ALTITUDE) if last_waypoint is not None:
elif isinstance(at, Group): heading = position.heading_between_point(last_waypoint.position)
group.add_waypoint(at.position, RTB_ALTITUDE) tod_location = position.point_from_heading(heading, RTB_DISTANCE)
elif issubclass(at, Airport): self._add_radio_waypoint(group, tod_location, last_waypoint.alt)
group.add_waypoint(at.position, RTB_ALTITUDE)
destination_waypoint = self._add_radio_waypoint(group, position, RTB_ALTITUDE)
if isinstance(at, Airport):
group.land_at(at) group.land_at(at)
return destination_waypoint
def _at_position(self, at) -> Point: def _at_position(self, at) -> Point:
if isinstance(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 = ControlledTask(OrbitAction(ATTACK_CIRCLE_ALT, pattern=OrbitAction.OrbitPattern.Circle))
orbit_task.stop_after_duration(ATTACK_CIRCLE_DURATION * 60) 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(orbit_task)
orbit_waypoint.tasks.append(EngageTargets(max_distance=DEFENCE_ENGAGEMENT_MAX_DISTANCE)) orbit_waypoint.tasks.append(EngageTargets(max_distance=DEFENCE_ENGAGEMENT_MAX_DISTANCE))
@ -263,9 +275,9 @@ class AircraftConflictGenerator:
client_count=client_count, client_count=client_count,
at=at and at or self._group_point(self.conflict.air_attackers_location)) 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: 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 group.task = CAS.name
self._setup_group(group, CAS, client_count) self._setup_group(group, CAS, client_count)
@ -312,11 +324,11 @@ class AircraftConflictGenerator:
location = self._group_point(self.conflict.air_defenders_location) location = self._group_point(self.conflict.air_defenders_location)
insertion_point = self.conflict.find_insertion_point(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: if self.conflict.is_vector:
destination_tail = self.conflict.tail.distance_to_point(insertion_point) > self.conflict.position.distance_to_point(insertion_point) 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 group.task = CAS.name
self._setup_group(group, CAS, client_count) self._setup_group(group, CAS, client_count)
@ -336,7 +348,7 @@ class AircraftConflictGenerator:
client_count=client_count, client_count=client_count,
at=at and at or self._group_point(self.conflict.air_attackers_location)) 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: for target_group in target_groups:
wayp.tasks.append(AttackGroup(target_group.id)) 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)) at=at and at or self._group_point(self.conflict.air_defenders_location))
group.task = CAP.name 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.EngageTargets(max_distance=DEFENCE_ENGAGEMENT_MAX_DISTANCE))
wayp.tasks.append(dcs.task.OrbitAction(ATTACK_CIRCLE_ALT, pattern=OrbitAction.OrbitPattern.Circle)) wayp.tasks.append(dcs.task.OrbitAction(ATTACK_CIRCLE_ALT, pattern=OrbitAction.OrbitPattern.Circle))
self._setup_group(group, CAP, client_count) self._setup_group(group, CAP, client_count)
@ -393,9 +405,9 @@ class AircraftConflictGenerator:
client_count=client_count, client_count=client_count,
at=at and at or self._group_point(self.conflict.air_attackers_location)) 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: 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 group.task = CAP.name
self._setup_group(group, CAP, client_count) self._setup_group(group, CAP, client_count)
@ -411,14 +423,14 @@ class AircraftConflictGenerator:
client_count=client_count, client_count=client_count,
at=at and at or self._group_point(self.conflict.air_defenders_location)) 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: 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: else:
heading = group.position.heading_between_point(self.conflict.position) heading = group.position.heading_between_point(self.conflict.position)
waypoint = group.add_waypoint(self.conflict.position.point_from_heading(heading, BARCAP_RACETRACK_DISTANCE), waypoint = self._add_radio_waypoint(group, self.conflict.position.point_from_heading(heading, BARCAP_RACETRACK_DISTANCE),
WARM_START_ALTITUDE, WARM_START_ALTITUDE,
WARM_START_AIRSPEED) WARM_START_AIRSPEED)
waypoint.tasks.append(OrbitAction(WARM_START_ALTITUDE, WARM_START_AIRSPEED)) waypoint.tasks.append(OrbitAction(WARM_START_ALTITUDE, WARM_START_AIRSPEED))
group.task = CAP.name group.task = CAP.name
@ -437,9 +449,11 @@ class AircraftConflictGenerator:
client_count=client_count, client_count=client_count,
at=self._group_point(self.conflict.air_defenders_location)) 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: if escort:
self.escort_targets.append((group, group.points.index(waypoint))) self.escort_targets.append((group, group.points.index(waypoint)))
self._add_radio_waypoint(group, destination.position, RTB_ALTITUDE)
group.task = Transport.name group.task = Transport.name
group.land_at(destination) group.land_at(destination)
@ -456,11 +470,11 @@ class AircraftConflictGenerator:
group.task = CAP.name group.task = CAP.name
group.points[0].tasks.append(EngageTargets(max_distance=INTERCEPT_MAX_DISTANCE)) 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)) wayp.tasks.append(EngageTargets(max_distance=INTERCEPT_MAX_DISTANCE))
if self.conflict.is_vector: 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._setup_group(group, CAP, client_count)
self._rtb_for(group, self.conflict.from_cp, at) 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) at=at and at or self._group_point(self.conflict.air_attackers_location)
) )
group.add_waypoint( self._add_radio_waypoint(group, self.conflict.position, HELI_ALT)
pos=self.conflict.position,
altitude=HELI_ALT,
)
self._setup_group(group, Transport, client_count) self._setup_group(group, Transport, client_count)

View File

@ -28,7 +28,7 @@ CAP_CAS_DISTANCE = 10000, 120000
GROUND_INTERCEPT_SPREAD = 5000 GROUND_INTERCEPT_SPREAD = 5000
GROUND_DISTANCE_FACTOR = 1 GROUND_DISTANCE_FACTOR = 1
GROUND_DISTANCE = 4000 GROUND_DISTANCE = 2000
GROUND_ATTACK_DISTANCE = 25000, 13000 GROUND_ATTACK_DISTANCE = 25000, 13000
@ -219,8 +219,8 @@ class Conflict:
pos = pos.point_from_heading(heading, 500) pos = pos.point_from_heading(heading, 500)
logging.info("Didn't find ground position!") logging.error("Didn't find ground position!")
return None return initial
@classmethod @classmethod
def capture_conflict(cls, attacker: Country, defender: Country, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater): 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) 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) position = Conflict._find_ground_position(initial_location, GROUND_INTERCEPT_SPREAD, _heading_sum(heading, 180), theater)
if not position: 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) position = to_cp.position.point_from_heading(heading, to_cp.size * GROUND_DISTANCE_FACTOR)
return cls( return cls(

View File

@ -9,6 +9,7 @@ from dcs.unit import Static
from theater import * from theater import *
from .conflictgen import * from .conflictgen import *
#from game.game import Game #from game.game import Game
from game import db
class MarkerSmoke(unittype.StaticType): class MarkerSmoke(unittype.StaticType):

0
tests/__init__.py Normal file
View File

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

80
tests/integration/util.py Normal file
View File

@ -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)

View File

View File

@ -56,7 +56,7 @@ class Base:
def _find_best_unit(self, dict, for_type: Task, count: int) -> typing.Dict: def _find_best_unit(self, dict, for_type: Task, count: int) -> typing.Dict:
if count <= 0: if count <= 0:
logging.info("{}: no units for {}".format(self, for_type)) logging.warning("{}: no units for {}".format(self, for_type))
return {} return {}
sorted_units = [key for key in dict.keys() if key in db.UNIT_BY_TASK[for_type]] sorted_units = [key for key in dict.keys() if key in db.UNIT_BY_TASK[for_type]]

View File

@ -10,30 +10,29 @@ from ui.styles import STYLES
from ui.window import * from ui.window import *
EVENT_DEPARTURE_MAX_DISTANCE = 340000
EVENT_COLOR_ATTACK = (100, 100, 255) EVENT_COLOR_ATTACK = (100, 100, 255)
EVENT_COLOR_DEFENSE = (255, 100, 100) 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: class OverviewCanvas:
mainmenu = None # type: ui.mainmenu.MainMenu mainmenu = None # type: ui.mainmenu.MainMenu
budget_label = None # type: Label
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
started = None started = None
ground_assets_icons = None # type: typing.Dict[str, pygame.Surface] 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.grid(column=0, row=0, sticky=NSEW) # Adds grid
self.wrapper.pack(side=LEFT) # packs window to the left 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 = Frame(self.wrapper, width=WIDTH, height=HEIGHT, borderwidth=2, **STYLES["frame-wrapper"])
self.embed.grid(column=0, row=0, sticky=NSEW) # Adds grid self.embed.grid(column=0, row=1, sticky=NSEW) # Adds grid
self.options = Frame(self.wrapper, borderwidth=2, **STYLES["frame-wrapper"]) 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.build_map_options_panel()
self.init_sdl_layer() self.init_sdl_layer()
self.init_sdl_thread() self.init_sdl_thread()
def build_map_options_panel(self): def build_map_options_panel(self):
def force_redraw():
if self.screen:
self.redraw_required = True
self.draw()
col = 0 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 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 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 col += 1
def map_size_toggle(self): def map_size_toggle(self):
@ -115,8 +112,8 @@ class OverviewCanvas:
self.options.configure(width=0) self.options.configure(width=0)
self.expanded = False self.expanded = False
else: else:
self.embed.configure(width=self.WIDTH) self.embed.configure(width=WIDTH)
self.options.configure(width=self.WIDTH) self.options.configure(width=WIDTH)
self.expanded = True self.expanded = True
def on_close(self): def on_close(self):
@ -131,8 +128,8 @@ class OverviewCanvas:
os.environ['SDL_VIDEODRIVER'] = 'windib' os.environ['SDL_VIDEODRIVER'] = 'windib'
# Create pygame 'screen' # Create pygame 'screen'
self.screen = pygame.display.set_mode((self.WIDTH, self.HEIGHT), pygame.DOUBLEBUF | pygame.HWSURFACE) self.screen = pygame.display.set_mode((WIDTH, HEIGHT), pygame.DOUBLEBUF | pygame.HWSURFACE)
self.screen.fill(pygame.Color(*self.BLACK)) self.screen.fill(pygame.Color(*BLACK))
# Load icons resources # Load icons resources
self.ground_assets_icons = {} self.ground_assets_icons = {}
@ -157,14 +154,14 @@ class OverviewCanvas:
# Load the map image # Load the map image
self.map = pygame.image.load(os.path.join("resources", self.game.theater.overview_image)).convert() 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, 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, WHITE, (0, 0, self.map.get_width(), self.map.get_height()), 5)
# Create surfaces for drawing # Create surfaces for drawing
self.surface = pygame.Surface((self.map.get_width() + self.MAP_PADDING * 2, self.surface = pygame.Surface((self.map.get_width() + MAP_PADDING * 2,
self.map.get_height() + self.MAP_PADDING * 2)) self.map.get_height() + MAP_PADDING * 2))
self.surface.set_alpha(None) 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 # Init pygame display
pygame.display.init() pygame.display.init()
@ -210,6 +207,10 @@ class OverviewCanvas:
if event.type == pygame.MOUSEMOTION: if event.type == pygame.MOUSEMOTION:
self.redraw_required = True self.redraw_required = True
elif event.type == pygame.MOUSEBUTTONDOWN: 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 # Scroll wheel
if event.button == 4: if event.button == 4:
self.zoom += 0.25 self.zoom += 0.25
@ -217,6 +218,7 @@ class OverviewCanvas:
elif event.button == 5: elif event.button == 5:
self.zoom -= 0.25 self.zoom -= 0.25
self.redraw_required = True self.redraw_required = True
"""
if event.button == 3: if event.button == 3:
right_down = True right_down = True
@ -239,8 +241,8 @@ class OverviewCanvas:
if self.redraw_required: if self.redraw_required:
# Fill # Fill
self.screen.fill(self.BACKGROUND) self.screen.fill(BACKGROUND)
self.surface.fill(self.BACKGROUND) self.surface.fill(BACKGROUND)
self.overlay.fill(pygame.Color(0, 0, 0, 0)) self.overlay.fill(pygame.Color(0, 0, 0, 0))
# Surface # Surface
@ -260,10 +262,10 @@ class OverviewCanvas:
self.redraw_required = False self.redraw_required = False
def draw_map(self, surface: pygame.Surface, overlay: pygame.Surface, mouse_pos: (int, int), mouse_down: [bool, bool]): 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 # 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.blit(zoom_lvl, (self.overlay.get_width()-zoom_lvl.get_width()-5,
self.overlay.get_height()-zoom_lvl.get_height()-5)) self.overlay.get_height()-zoom_lvl.get_height()-5))
@ -281,7 +283,7 @@ class OverviewCanvas:
elif connected_cp.captured and cp.captured: elif connected_cp.captured and cp.captured:
color = self._player_color() color = self._player_color()
else: else:
color = self.BLACK color = BLACK
pygame.draw.line(surface, color, coords, connected_coords, 2) pygame.draw.line(surface, color, coords, connected_coords, 2)
@ -325,12 +327,12 @@ class OverviewCanvas:
else: else:
color = self._enemy_color() 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)) 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) label = self.font.render(cp.name, ANTIALIASING, (225, 225, 225), BLACK)
labelHover = self.font.render(cp.name, self.ANTIALIASING, (255, 255, 255), (128, 186, 128)) labelHover = self.font.render(cp.name, ANTIALIASING, (255, 255, 255), (128, 186, 128))
labelClick = self.font.render(cp.name, self.ANTIALIASING, (255, 255, 255), (122, 122, 255)) labelClick = self.font.render(cp.name, ANTIALIASING, (255, 255, 255), (122, 122, 255))
point = coords[0] - label.get_width() / 2 + 1, coords[1] + 1 point = coords[0] - label.get_width() / 2 + 1, coords[1] + 1
rect = pygame.Rect(*point, label.get_width(), label.get_height()) 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)) self.draw_base_info(self.overlay, cp, (0, 0))
if self.selected_event_info: if self.selected_event_info:
if self._cp_available_for_selected_event(cp): 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: else:
self.surface.blit(label, (coords[0] - label.get_width() / 2 + 1, coords[1] + 1)) self.surface.blit(label, (coords[0] - label.get_width() / 2 + 1, coords[1] + 1))
if self.display_forces.get(): if self.display_forces.get():
units_title = " {} / {} / {} ".format(cp.base.total_planes, cp.base.total_armor, cp.base.total_aa) 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)) self.surface.blit(label2, (coords[0] - label2.get_width() / 2, coords[1] + label.get_height() + 1))
return mouse_down return mouse_down
def draw_base_info(self, surface: pygame.Surface, control_point: ControlPoint, pos): 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) title = self.font.render(control_point.name, ANTIALIASING, BLACK, GREEN)
hp = self.font.render("Strength : ", self.ANTIALIASING, (225, 225, 225), self.BLACK) hp = self.font.render("Strength : ", ANTIALIASING, (225, 225, 225), BLACK)
armor_txt = "ARMOR > " armor_txt = "ARMOR > "
for key, value in control_point.base.armor.items(): for key, value in control_point.base.armor.items():
armor_txt += key.id + " x " + str(value) + " | " 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 > " aircraft_txt = "AIRCRAFT > "
for key, value in control_point.base.aircraft.items(): for key, value in control_point.base.aircraft.items():
aircraft_txt += key.id + " x " + str(value) + " | " 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 > " aa_txt = "AA/SAM > "
for key, value in control_point.base.aa.items(): for key, value in control_point.base.aa.items():
aa_txt += key.id + " x " + str(value) + " | " 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() lineheight = title.get_height()
w = max([max([a.get_width() for a in [title, armor, aircraft, aa]]), 150]) w = max([max([a.get_width() for a in [title, armor, aircraft, aa]]), 150])
h = 5 * lineheight + 4 * 5 h = 5 * lineheight + 4 * 5
# Draw frame # Draw frame
pygame.draw.rect(surface, self.GREEN, (pos[0], pos[1], w + 8, h + 8)) pygame.draw.rect(surface, 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, 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] + 2, pos[1], w + 4, lineheight + 4))
# Title # Title
surface.blit(title, (pos[0] + 4, 4 + pos[1])) surface.blit(title, (pos[0] + 4, 4 + pos[1]))
surface.blit(hp, (pos[0] + 4, 4 + pos[1] + lineheight + 5)) surface.blit(hp, (pos[0] + 4, 4 + pos[1] + lineheight + 5))
# Draw gauge # 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)) (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)) (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)) pos[0] + hp.get_width() + 5, 4 + pos[1] + lineheight + 5 + 2, 50 * control_point.base.strength, lineheight - 4))
# Text # Text
@ -405,8 +407,8 @@ class OverviewCanvas:
def draw_selected_event_info(self): def draw_selected_event_info(self):
event = self.selected_event_info[0] event = self.selected_event_info[0]
title = self.font.render(str(event), self.ANTIALIASING, self.BLACK, self.GREEN) title = self.font.render(str(event), ANTIALIASING, BLACK, GREEN)
hint = self.font.render("Select CP to depart from.", self.ANTIALIASING, (225, 225, 225), self.BLACK) hint = self.font.render("Select CP to depart from.", ANTIALIASING, (225, 225, 225), BLACK)
w = hint.get_width() w = hint.get_width()
h = title.get_height() + hint.get_height() + 20 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 pos = self.overlay.get_width() / 2 - w / 2, self.overlay.get_height() - h
# Draw frame # Draw frame
pygame.draw.rect(self.overlay, self.GREEN, (pos[0], pos[1], w + 8, h + 8)) pygame.draw.rect(self.overlay, 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, 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] + 2, pos[1], w + 4, title.get_height() + 4))
# Title # Title
self.overlay.blit(title, (pos[0] + 4, 4 + pos[1])) 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) 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): 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])) surface.blit(lb, (pos[0] + 18, pos[1]))
def draw_events(self, surface: pygame.Surface, mouse_pos, mouse_down): 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 rect.collidepoint(*mouse_pos) or self.selected_event_info == (event, rect.center):
if not label_to_draw: 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 rect.collidepoint(*mouse_pos):
if mouse_down[0]: if mouse_down[0]:
@ -517,7 +519,7 @@ class OverviewCanvas:
def _selected_cp(self, cp): def _selected_cp(self, cp):
if self.selected_event_info: 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 = self.selected_event_info[0]
event.departure_cp = cp event.departure_cp = cp
@ -551,8 +553,8 @@ class OverviewCanvas:
X = point_b_img[1] + X_offset * X_scale X = point_b_img[1] + X_offset * X_scale
Y = point_a_img[0] - Y_offset * Y_scale Y = point_a_img[0] - Y_offset * Y_scale
X += self.MAP_PADDING X += MAP_PADDING
Y += self.MAP_PADDING Y += MAP_PADDING
return X > treshold and X or treshold, Y > treshold and Y or treshold 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: def _cp_available_for_selected_event(self, cp: ControlPoint) -> bool:
event = self.selected_event_info[0] event = self.selected_event_info[0]
return event.is_departure_available_from(cp)
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
def _player_color(self): 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): 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): def update(self):
self.redraw_required = True self.redraw_required = True
self.draw() self.draw()
self.budget_label.text = "Budget: {}m (+{}m)".format(self.game.budget, self.game.budget_reward_amount)
def compute_display_rules(self): 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]]) return sum([1 if a.get() else 0 for a in [self.display_forces, self.display_road, self.display_bases, self.display_ground_targets]])

View File

@ -80,7 +80,7 @@ class Debriefing:
nonlocal dead_units nonlocal dead_units
object_mission_id = int(object_mission_id_str) object_mission_id = int(object_mission_id_str)
if object_mission_id in dead_units: 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 return
dead_units.append(object_mission_id) dead_units.append(object_mission_id)