mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
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:
parent
fbbe56f954
commit
f7e2c8921c
17
game/db.py
17
game/db.py
@ -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,
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
@ -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)
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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={})
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
14
game/game.py
14
game/game.py
@ -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)
|
||||||
|
|||||||
@ -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")
|
||||||
|
|||||||
@ -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,12 +423,12 @@ 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))
|
||||||
@ -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)
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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
0
tests/__init__.py
Normal file
10
tests/integration/__init__.py
Normal file
10
tests/integration/__init__.py
Normal 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()
|
||||||
46
tests/integration/baseattack.py
Normal file
46
tests/integration/baseattack.py
Normal 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()
|
||||||
50
tests/integration/convoystrike.py
Normal file
50
tests/integration/convoystrike.py
Normal 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()
|
||||||
52
tests/integration/frontlineattack.py
Normal file
52
tests/integration/frontlineattack.py
Normal 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()
|
||||||
48
tests/integration/insurgentattack.py
Normal file
48
tests/integration/insurgentattack.py
Normal 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()
|
||||||
48
tests/integration/intercept.py
Normal file
48
tests/integration/intercept.py
Normal 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()
|
||||||
49
tests/integration/navalintercept.py
Normal file
49
tests/integration/navalintercept.py
Normal 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()
|
||||||
48
tests/integration/strike.py
Normal file
48
tests/integration/strike.py
Normal 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
80
tests/integration/util.py
Normal 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)
|
||||||
0
tests/userfolder/DCS/Missions/empty.txt
Normal file
0
tests/userfolder/DCS/Missions/empty.txt
Normal 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]]
|
||||||
|
|||||||
@ -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]])
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user