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 = {
# 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,

View File

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

View File

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

View File

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

View File

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

View File

@@ -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={})

View File

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

View File

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

View File

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