save game version check; mission description in briefings; minor fixes and improvements; predefined ground objects (statics) support; strike mission WIP

This commit is contained in:
Vasyl Horbachenko 2018-09-09 01:21:32 +03:00
parent 2179e4af47
commit e0d82da6cb
37 changed files with 588 additions and 220 deletions

View File

@ -17,10 +17,13 @@ from game.game import Game
from theater import start_generator
from userdata import persistency, logging as logging_module
assert len(sys.argv) == 3, "__init__.py should be started with two mandatory arguments: %UserProfile% location and application version"
persistency.setup(sys.argv[1])
dcs.planes.FlyingType.payload_dirs = [os.path.join(os.path.dirname(os.path.realpath(__file__)), "resources\\payloads")]
logging_module.setup_version_string(sys.argv[2])
VERSION_STRING = sys.argv[2]
logging_module.setup_version_string(VERSION_STRING)
logging.info("Using {} as userdata folder".format(persistency.base_path()))
@ -29,10 +32,20 @@ def proceed_to_main_menu(game: Game):
m.display()
def is_version_compatible(save_version):
current_version = VERSION_STRING.split(".")
save_version = save_version.split(".")
if current_version[:2] == save_version[:2]:
return True
return False
w = ui.window.Window()
try:
game = persistency.restore_game()
if not game:
if not game or not is_version_compatible(game.settings.version):
new_game_menu = None # type: NewGameMenu
def start_new_game(player_name: str, enemy_name: str, terrain: str, sams: bool, midgame: bool, multiplier: float):
@ -54,6 +67,7 @@ try:
game.budget = int(game.budget * multiplier)
game.settings.multiplier = multiplier
game.settings.sams = sams
game.settings.version = VERSION_STRING
if midgame:
game.budget = game.budget * 4 * len(list(conflicttheater.conflicts()))

View File

@ -100,7 +100,6 @@ PRICES = {
AirDefence.AAA_Vulcan_M163: 5,
AirDefence.SAM_Avenger_M1097: 10,
AirDefence.SAM_Patriot_ICC: 15,
AirDefence.AAA_ZU_23_on_Ural_375: 5,
AirDefence.SAM_SA_18_Igla_S_MANPADS: 8,
@ -185,7 +184,6 @@ UNIT_BY_TASK = {
AirDefence.SAM_Avenger_M1097,
AirDefence.SAM_Avenger_M1097,
AirDefence.SAM_Avenger_M1097,
AirDefence.SAM_Patriot_ICC,
AirDefence.AAA_ZU_23_on_Ural_375,
AirDefence.AAA_ZU_23_on_Ural_375,
@ -208,7 +206,6 @@ Units from AirDefense category of UNIT_BY_TASK that will be removed from use if
"""
SAM_BAN = [
AirDefence.SAM_Avenger_M1097,
AirDefence.SAM_Patriot_ICC,
AirDefence.SAM_SA_19_Tunguska_2S6,
AirDefence.SAM_SA_8_Osa_9A33,
@ -233,7 +230,7 @@ AirDefense units that will be spawned at control points not related to the curre
"""
EXTRA_AA = {
"Russia": AirDefence.SAM_SA_9_Strela_1_9P31,
"USA": AirDefence.SAM_Patriot_EPP_III,
"USA": AirDefence.SAM_Avenger_M1097,
}
"""
@ -318,7 +315,6 @@ UNIT_BY_COUNTRY = {
AirDefence.AAA_Vulcan_M163,
AirDefence.SAM_Avenger_M1097,
AirDefence.SAM_Patriot_ICC,
CVN_74_John_C__Stennis,
LHA_1_Tarawa,

View File

@ -4,6 +4,6 @@ from .frontlinepatrol import *
from .intercept import *
from .baseattack import *
from .navalintercept import *
from .antiaastrike import *
from .insurgentattack import *
from .infantrytransport import *
from .strike import *

View File

@ -1,86 +0,0 @@
import math
import random
from dcs.task import *
from game import *
from game.event import *
from game.operation.antiaastrike import AntiAAStrikeOperation
from userdata.debriefing import Debriefing
class AntiAAStrikeEvent(Event):
TARGET_AMOUNT_MAX = 2
STRENGTH_INFLUENCE = 0.3
SUCCESS_TARGETS_HIT_PERCENTAGE = 0.5
targets = None # type: db.ArmorDict
def __str__(self):
return "Anti-AA strike"
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():
if unit in self.targets:
destroyed_targets += count
if self.from_cp.captured:
return math.ceil(float(destroyed_targets) / total_targets) >= self.SUCCESS_TARGETS_HIT_PERCENTAGE
else:
return math.ceil(float(destroyed_targets) / total_targets) < self.SUCCESS_TARGETS_HIT_PERCENTAGE
def commit(self, debriefing: Debriefing):
super(AntiAAStrikeEvent, self).commit(debriefing)
if self.from_cp.captured:
if self.is_successfull(debriefing):
self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
else:
self.to_cp.base.affect_strength(+self.STRENGTH_INFLUENCE)
else:
if self.is_successfull(debriefing):
self.from_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
else:
self.to_cp.base.affect_strength(-self.STRENGTH_INFLUENCE)
def skip(self):
if self.to_cp.captured:
self.to_cp.base.affect_strength(-0.1)
def player_attacking(self, strikegroup: db.PlaneDict, clients: db.PlaneDict):
self.targets = self.to_cp.base.assemble_aa(count=self.to_cp.base.total_aa)
op = AntiAAStrikeOperation(game=self.game,
attacker_name=self.attacker_name,
defender_name=self.defender_name,
attacker_clients=clients,
defender_clients={},
from_cp=self.from_cp,
to_cp=self.to_cp)
op.setup(target=self.targets,
strikegroup=strikegroup,
interceptors={})
self.operation = op
def player_defending(self, interceptors: db.PlaneDict, clients: db.PlaneDict):
self.targets = self.to_cp.base.assemble_aa()
op = AntiAAStrikeOperation(
self.game,
attacker_name=self.attacker_name,
defender_name=self.defender_name,
attacker_clients={},
defender_clients=clients,
from_cp=self.from_cp,
to_cp=self.to_cp
)
strikegroup = self.from_cp.base.scramble_cas(self.game.settings.multiplier)
op.setup(target=self.targets,
strikegroup=strikegroup,
interceptors=interceptors)
self.operation = op

View File

@ -1,3 +1,5 @@
import logging
from dcs.unittype import UnitType
from game import *
@ -71,8 +73,24 @@ class Event:
else:
cp = self.to_cp
logging.info("base {} commit losses {}".format(cp.base, losses))
cp.base.commit_losses(losses)
for object_identifier in debriefing.destroyed_objects:
for cp in self.game.theater.controlpoints:
remove_ids = []
if not cp.ground_objects:
continue
for i, ground_object in enumerate(cp.ground_objects):
if ground_object.matches_string_identifier(object_identifier):
logging.info("cp {} removing ground object {}".format(cp, ground_object.string_identifier))
remove_ids.append(i)
remove_ids.reverse()
for i in remove_ids:
del cp.ground_objects[i]
def skip(self):
pass

View File

@ -20,7 +20,7 @@ class InterceptEvent(Event):
transport_unit = None # type: FlyingType
def __str__(self):
return "Intercept"
return "Air Intercept"
def _enemy_scramble_multiplier(self) -> float:
is_global = self.from_cp.is_global or self.to_cp.is_global

46
game/event/strike.py Normal file
View File

@ -0,0 +1,46 @@
import math
import random
from dcs.task import *
from dcs.vehicles import *
from game import db
from game.operation.strike import StrikeOperation
from theater.conflicttheater import *
from userdata.debriefing import Debriefing
from .event import Event
class StrikeEvent(Event):
STRENGTH_INFLUENCE = 0.0
SINGLE_OBJECT_STRENGTH_INFLUENCE = 0.03
def __str__(self):
return "Strike"
def is_successfull(self, debriefing: Debriefing):
return True
def commit(self, debriefing: Debriefing):
super(StrikeEvent, self).commit(debriefing)
self.to_cp.base.affect_strength(-self.SINGLE_OBJECT_STRENGTH_INFLUENCE * len(debriefing.destroyed_objects))
def player_attacking(self, strikegroup: db.PlaneDict, escort: db.PlaneDict, clients: db.PlaneDict):
op = StrikeOperation(
self.game,
attacker_name=self.attacker_name,
defender_name=self.defender_name,
attacker_clients=clients,
defender_clients={},
from_cp=self.from_cp,
to_cp=self.to_cp
)
interceptors = self.to_cp.base.scramble_interceptors(self.game.settings.multiplier)
op.setup(strikegroup=strikegroup,
escort=escort,
interceptors=interceptors)
self.operation = op

View File

@ -52,10 +52,10 @@ EVENT_PROBABILITIES = {
BaseAttackEvent: [100, 10],
FrontlineAttackEvent: [100, 0],
FrontlinePatrolEvent: [100, 0],
StrikeEvent: [100, 0],
InterceptEvent: [25, 10],
InsurgentAttackEvent: [0, 10],
NavalInterceptEvent: [25, 10],
AntiAAStrikeEvent: [25, 10],
InfantryTransportEvent: [25, 0],
}

View File

@ -1,53 +0,0 @@
from dcs.terrain import Terrain
from game import db
from gen.armor import *
from gen.aircraft import *
from gen.aaa import *
from gen.shipgen import *
from gen.triggergen import *
from gen.airsupportgen import *
from gen.visualgen import *
from gen.conflictgen import Conflict
from .operation import Operation
class AntiAAStrikeOperation(Operation):
strikegroup = None # type: db.PlaneDict
interceptors = None # type: db.PlaneDict
target = None # type: db.ArmorDict
def setup(self,
target: db.ArmorDict,
strikegroup: db.PlaneDict,
interceptors: db.PlaneDict):
self.strikegroup = strikegroup
self.interceptors = interceptors
self.target = target
def prepare(self, terrain: Terrain, is_quick: bool):
super(AntiAAStrikeOperation, self).prepare(terrain, is_quick)
if self.defender_name == self.game.player:
self.attackers_starting_position = None
self.defenders_starting_position = None
conflict = Conflict.ground_base_attack(
attacker=self.mission.country(self.attacker_name),
defender=self.mission.country(self.defender_name),
from_cp=self.from_cp,
to_cp=self.to_cp,
theater=self.game.theater
)
self.initialize(mission=self.mission,
conflict=conflict)
def generate(self):
self.airgen.generate_cas_strikegroup(self.strikegroup, clients=self.attacker_clients, at=self.attackers_starting_position)
if self.interceptors:
self.airgen.generate_defense(self.interceptors, clients=self.defender_clients, at=self.defenders_starting_position)
self.armorgen.generate({}, self.target)
super(AntiAAStrikeOperation, self).generate()

View File

@ -63,5 +63,8 @@ class BaseAttackOperation(Operation):
self.airgen.generate_attackers_escort(self.escort, clients=self.attacker_clients, at=self.attackers_starting_position)
self.visualgen.generate_target_smokes(self.to_cp)
self.briefinggen.title = "Base attack"
self.briefinggen.description = "The goal of an attacker is to lower defender presence by destroying their armor and aircraft. Base will be considered captured if attackers on the ground overrun the defenders. Be advised that your flight will not attack anything until you explicitly tell them so by comms menu."
super(BaseAttackOperation, self).generate()

View File

@ -51,4 +51,7 @@ class FrontlineAttackOperation(Operation):
def generate(self):
self.armorgen.generate_vec(self.attackers, self.target)
self.airgen.generate_cas_strikegroup(self.strikegroup, clients=self.attacker_clients, at=self.attackers_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."
super(FrontlineAttackOperation, self).generate()

View File

@ -52,7 +52,10 @@ class FrontlinePatrolOperation(Operation):
def generate(self):
self.airgen.generate_defenders_cas(self.cas, {}, self.defenders_starting_position)
self.airgen.generate_defenders_escort(self.escort, {}, self.defenders_starting_position)
self.airgen.generate_patrol(self.interceptors, self.attacker_clients, self.attackers_starting_position)
self.airgen.generate_migcap(self.interceptors, self.attacker_clients, self.attackers_starting_position)
self.armorgen.generate_vec(self.armor_attackers, self.armor_defenders)
self.briefinggen.title = "Frontline CAP"
self.briefinggen.description = "Providing CAP support for ground units attacking enemy lines. Enemy will scramble its CAS and your task is to intercept it. Operation will be considered successful if total number of friendly units will be lower than enemy by at least a factor of 0.8 (i.e. with 12 units from both sides, there should be at least 8 friendly units alive), lowering targets strength as a result."
super(FrontlinePatrolOperation, self).generate()

View File

@ -48,6 +48,9 @@ class InfantryTransportOperation(Operation):
self.visualgen.generate_transportation_marker(self.conflict.ground_attackers_location)
self.visualgen.generate_transportation_destination(self.conflict.position)
self.briefinggen.title = "Infantry transport"
self.briefinggen.description = "Helicopter operation to transport infantry troops from the base to the front line. Lowers target strength"
# TODO: horrible, horrible hack
# this will disable vehicle activation triggers,
# which aren't needed on this type of missions

View File

@ -41,4 +41,7 @@ class InsurgentAttackOperation(Operation):
self.airgen.generate_defense(self.strikegroup, self.defender_clients, self.defenders_starting_position)
self.armorgen.generate(self.target, {})
self.briefinggen.title = "Destroy insurgents"
self.briefinggen.description = "Destroy vehicles of insurgents in close proximity of the friendly base. Be advised that your flight will not attack anything until you explicitly tell them so by comms menu."
super(InsurgentAttackOperation, self).generate()

View File

@ -55,5 +55,9 @@ class InterceptOperation(Operation):
self.airgen.generate_defenders_escort(self.escort, clients=self.defender_clients)
self.airgen.generate_interception(self.interceptors, clients=self.attacker_clients, at=self.attackers_starting_position)
self.briefinggen.title = "Air Intercept"
self.briefinggen.description = "Intercept enemy supply transport aircraft. Escort will also be present if there are available planes on the base. Operation will be considered successful if most of the targets are destroyed, lowering targets strength as a result"
super(InterceptOperation, self).generate()

View File

@ -34,8 +34,6 @@ class NavalInterceptionOperation(Operation):
self.initialize(self.mission, conflict)
def generate(self):
super(NavalInterceptionOperation, self).generate()
target_groups = self.shipgen.generate_cargo(units=self.targets)
self.airgen.generate_ship_strikegroup(
@ -51,3 +49,9 @@ class NavalInterceptionOperation(Operation):
clients=self.defender_clients,
at=self.defenders_starting_position
)
self.briefinggen.title = "Naval Intercept"
self.briefinggen.description = "Destroy supply transport ships. Lowers target strength. Be advised that your flight will not attack anything until you explicitly tell them so by comms menu."
super(NavalInterceptionOperation, self).generate()

View File

@ -18,9 +18,11 @@ class Operation:
extra_aagen = None # type: ExtraAAConflictGenerator
shipgen = None # type: ShipGenerator
triggersgen = None # type: TriggersGenerator
awacsgen = None # type: AirSupportConflictGenerator
airsupportgen = None # type: AirSupportConflictGenerator
visualgen = None # type: VisualGenerator
envgen = None # type: EnvironmentGenerator
groundobjectgen = None # type: GroundObjectsGenerator
briefinggen = None # type: BriefingGenerator
environment_settings = None
trigger_radius = TRIGGER_RADIUS_MEDIUM
@ -44,6 +46,12 @@ class Operation:
self.to_cp = to_cp
self.is_quick = False
def units_of(self, country_name: str) -> typing.Collection[UnitType]:
return []
def is_successfull(self, debriefing: Debriefing) -> bool:
return True
def initialize(self, mission: Mission, conflict: Conflict):
self.mission = mission
self.conflict = conflict
@ -52,10 +60,12 @@ class Operation:
self.airgen = AircraftConflictGenerator(mission, conflict, self.game.settings)
self.aagen = AAConflictGenerator(mission, conflict)
self.shipgen = ShipGenerator(mission, conflict)
self.awacsgen = AirSupportConflictGenerator(mission, conflict, self.game)
self.airsupportgen = AirSupportConflictGenerator(mission, conflict, self.game)
self.triggersgen = TriggersGenerator(mission, conflict, self.game)
self.visualgen = VisualGenerator(mission, conflict, self.game)
self.envgen = EnviromentGenerator(mission, conflict, self.game)
self.groundobjectgen = GroundObjectsGenerator(mission, conflict, self.game)
self.briefinggen = BriefingGenerator(mission, conflict, self.game)
player_name = self.from_cp.captured and self.attacker_name or self.defender_name
enemy_name = self.from_cp.captured and self.defender_name or self.attacker_name
@ -78,10 +88,18 @@ class Operation:
def generate(self):
self.visualgen.generate()
self.awacsgen.generate(self.is_awacs_enabled)
# air support
self.airsupportgen.generate(self.is_awacs_enabled)
self.briefinggen.append_frequency("Tanker", "10X/240 MHz FM")
if self.is_awacs_enabled:
self.briefinggen.append_frequency("AWACS", "244 MHz FM")
# ground infrastructure
self.groundobjectgen.generate()
self.extra_aagen.generate()
# triggers
if self.game.is_player_attack(self.conflict.attackers_side):
cp = self.conflict.from_cp
else:
@ -92,13 +110,16 @@ class Operation:
activation_trigger_radius=self.trigger_radius,
awacs_enabled=self.is_awacs_enabled)
# env settings
if self.environment_settings is None:
self.environment_settings = self.envgen.generate()
else:
self.envgen.load(self.environment_settings)
def units_of(self, country_name: str) -> typing.Collection[UnitType]:
return []
# main frequencies
self.briefinggen.append_frequency("Flight", "251 MHz FM")
if self.conflict.from_cp.is_global or self.conflict.to_cp.is_global:
self.briefinggen.append_frequency("Carrier", "20X/ICLS CHAN1")
def is_successfull(self, debriefing: Debriefing) -> bool:
return True
# briefing
self.briefinggen.generate()

71
game/operation/strike.py Normal file
View File

@ -0,0 +1,71 @@
from dcs.terrain import Terrain
from game import db
from gen.armor import *
from gen.aircraft import *
from gen.aaa import *
from gen.shipgen import *
from gen.triggergen import *
from gen.airsupportgen import *
from gen.visualgen import *
from gen.conflictgen import Conflict
from .operation import Operation
class StrikeOperation(Operation):
strikegroup = None # type: db.PlaneDict
escort = None # type: db.PlaneDict
interceptors = None # type: db.PlaneDict
def setup(self,
strikegroup: db.PlaneDict,
escort: db.PlaneDict,
interceptors: db.PlaneDict):
self.strikegroup = strikegroup
self.escort = escort
self.interceptors = interceptors
def prepare(self, terrain: Terrain, is_quick: bool):
super(StrikeOperation, self).prepare(terrain, is_quick)
self.defenders_starting_position = None
if self.game.player == self.defender_name:
self.attackers_starting_position = None
conflict = Conflict.capture_conflict(
attacker=self.mission.country(self.attacker_name),
defender=self.mission.country(self.defender_name),
from_cp=self.from_cp,
to_cp=self.to_cp,
theater=self.game.theater
)
self.initialize(mission=self.mission,
conflict=conflict)
def generate(self):
targets = [] # type: typing.List[typing.Tuple[str, Point]]
category_counters = {} # type: typing.Dict[str, int]
for object in self.to_cp.ground_objects:
category_counters[object.category] = category_counters.get(object.category, 0) + 1
markpoint_name = "{}{}".format(object.name_abbrev, category_counters[object.category])
targets.append((markpoint_name, object.position))
self.briefinggen.append_target(str(object), markpoint_name)
targets.sort(key=lambda x: self.from_cp.position.distance_to_point(x[1]))
self.airgen.generate_ground_attack_strikegroup(strikegroup=self.strikegroup,
targets=targets,
clients=self.attacker_clients,
at=self.attackers_starting_position)
self.airgen.generate_attackers_escort(self.escort,
clients=self.attacker_clients,
at=self.attackers_starting_position)
self.airgen.generate_barcap(patrol=self.interceptors,
clients={},
at=self.defenders_starting_position)
super(StrikeOperation, self).generate()

View File

@ -7,3 +7,4 @@ class Settings:
multiplier = 1
sams = True
cold_start = False
version = None

View File

@ -7,6 +7,8 @@ from .shipgen import *
from .visualgen import *
from .triggergen import *
from .environmentgen import *
from .groundobjectsgen import *
from .briefinggen import *
from . import naming

View File

@ -1,6 +1,3 @@
from game import *
from theater.conflicttheater import ConflictTheater
from .conflictgen import *
from .naming import *
@ -61,6 +58,7 @@ class ExtraAAConflictGenerator:
if cp.position.distance_to_point(self.conflict.from_cp.position) < EXTRA_AA_MIN_DISTANCE:
continue
print("generated extra aa for {}".format(cp))
country_name = cp.captured and self.player_name or self.enemy_name
position = cp.position.point_from_heading(0, EXTRA_AA_POSITION_FROM_CP)
@ -69,6 +67,6 @@ class ExtraAAConflictGenerator:
name=namegen.next_basedefense_name(),
_type=db.EXTRA_AA[country_name],
position=position,
group_size=2
group_size=1
)

View File

@ -265,6 +265,30 @@ class AircraftConflictGenerator:
self.escort_targets.append((group, group.points.index(waypoint)))
self._rtb_for(group, self.conflict.from_cp, at)
def generate_ground_attack_strikegroup(self, strikegroup: db.PlaneDict, targets: typing.List[typing.Tuple[str, Point]], clients: db.PlaneDict, at: db.StartingPosition = None):
assert len(self.escort_targets) == 0
for flying_type, count, client_count in self._split_to_groups(strikegroup, clients):
group = self._generate_group(
name=namegen.next_unit_name(self.conflict.attackers_side, flying_type),
side=self.conflict.attackers_side,
unit_type=flying_type,
count=count,
client_count=client_count,
at=at and at or self._group_point(self.conflict.air_attackers_location))
escort_until_waypoint = None
for name, pos in targets:
waypoint = group.add_waypoint(pos, WARM_START_ALTITUDE, WARM_START_AIRSPEED, self.m.translation.create_string(name))
if escort_until_waypoint is None:
escort_until_waypoint = waypoint
group.task = CAS.name
self._setup_group(group, CAS, client_count)
self.escort_targets.append((group, group.points.index(escort_until_waypoint)))
self._rtb_for(group, self.conflict.from_cp, at)
def generate_defenders_cas(self, defenders: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None):
assert len(self.escort_targets) == 0
@ -348,7 +372,7 @@ class AircraftConflictGenerator:
self._setup_group(group, CAP, client_count)
self._rtb_for(group, self.conflict.to_cp, at)
def generate_patrol(self, patrol: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None):
def generate_migcap(self, patrol: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None):
for flying_type, count, client_count in self._split_to_groups(patrol, clients):
group = self._generate_group(
name=namegen.next_unit_name(self.conflict.attackers_side, flying_type),
@ -366,6 +390,26 @@ class AircraftConflictGenerator:
self._setup_group(group, CAP, client_count)
self._rtb_for(group, self.conflict.from_cp, at)
def generate_barcap(self, patrol: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None):
for flying_type, count, client_count in self._split_to_groups(patrol, clients):
group = self._generate_group(
name=namegen.next_unit_name(self.conflict.defenders_side, flying_type),
side=self.conflict.defenders_side,
unit_type=flying_type,
count=count,
client_count=client_count,
at=at and at or self._group_point(self.conflict.air_defenders_location))
waypoint = group.add_waypoint(self.conflict.position, WARM_START_ALTITUDE, WARM_START_AIRSPEED)
if self.conflict.is_vector:
group.add_waypoint(self.conflict.tail, WARM_START_ALTITUDE, WARM_START_AIRSPEED)
else:
waypoint.tasks.append(OrbitAction(WARM_START_ALTITUDE, WARM_START_AIRSPEED))
group.task = CAP.name
self._setup_group(group, CAP, client_count)
self._rtb_for(group, self.conflict.to_cp, at)
def generate_transport(self, transport: db.PlaneDict, destination: Airport):
assert len(self.escort_targets) == 0

49
gen/briefinggen.py Normal file
View File

@ -0,0 +1,49 @@
import logging
from game import db
from .conflictgen import *
from .naming import *
from dcs.mission import *
class BriefingGenerator:
freqs = None # type: typing.List[typing.Tuple[str, str]]
title = "" # type: str
description = "" # type: str
targets = None # type: typing.List[typing.Tuple[str, str]]
def __init__(self, mission: Mission, conflict: Conflict, game):
self.m = mission
self.conflict = conflict
self.game = game
self.freqs = []
self.targets = []
def append_frequency(self, name: str, frequency: str):
self.freqs.append((name, frequency))
def append_target(self, description: str, markpoint: str = None):
self.targets.append((description, markpoint))
def generate(self):
description = ""
if self.title:
description += self.title
if self.description:
description += "\n\n" + self.description
if self.freqs:
description += "\n\n RADIO:"
for name, freq in self.freqs:
description += "\n{}: {}".format(name, freq)
if self.targets:
description += "\n\n TARGETS:"
for name, tp in self.targets:
description += "\n{} {}".format(name, "(TP {})".format(tp) if tp else "")
self.m.set_description_text(description)

57
gen/groundobjectsgen.py Normal file
View File

@ -0,0 +1,57 @@
import logging
from game import db
from .conflictgen import *
from .naming import *
from dcs.mission import *
from dcs.statics import *
CATEGORY_MAPPING = {
"power": [Fortification.Workshop_A],
"warehouse": [Warehouse.Warehouse],
"fuel": [Warehouse.Tank],
"ammo": [Warehouse.Ammunition_depot],
}
class GroundObjectsGenerator:
def __init__(self, mission: Mission, conflict: Conflict, game):
self.m = mission
self.conflict = conflict
self.game = game
def generate(self):
side = self.m.country(self.game.enemy)
cp = None # type: ControlPoint
if self.conflict.attackers_side.name == self.game.player:
cp = self.conflict.to_cp
else:
cp = self.conflict.from_cp
for ground_object in cp.ground_objects:
if ground_object.category == "defense":
unit_type = random.choice(self.game.commision_unit_types(cp, AirDefence))
assert unit_type is not None, "Cannot find unit type for GroundObject defense ({})!".format(cp)
group = self.m.vehicle_group(
country=side,
name=ground_object.string_identifier,
_type=unit_type,
position=Point(*ground_object.location),
heading=ground_object.heading
)
logging.info("generated defense object identifier {} with mission id {}".format(group.name, group.id))
else:
group = self.m.static_group(
country=side,
name=ground_object.string_identifier,
_type=random.choice(CATEGORY_MAPPING[ground_object.category]),
position=Point(*ground_object.location),
heading=ground_object.heading
)
logging.info("generated object identifier {} with mission id {}".format(group.name, group.id))

View File

@ -146,19 +146,6 @@ class TriggersGenerator:
self._set_skill(player_coalition, enemy_coalition)
self._set_allegiances(player_coalition, enemy_coalition)
description = ""
description += "FREQUENCIES:"
description += "\nFlight: 251 MHz AM"
description += "\nTanker: 10X/240 MHz"
if awacs_enabled:
description += "\nAWACS: 244 MHz"
if self.conflict.from_cp.is_global or self.conflict.to_cp.is_global:
description += "\nCarrier: 20X/ICLS CHAN1"
self.mission.set_description_text(description)
if not is_quick:
# TODO: waypoint parts of this should not be post-hacked but added in airgen
self._gen_activation_trigger(activation_trigger_radius, player_cp, player_coalition, enemy_coalition)

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,44 @@
import pickle
import typing
from game import db
from gen.groundobjectsgen import TheaterGroundObject
from dcs.mission import Mission
from dcs.terrain import PersianGulf
m = Mission()
m.load_file("./cau_groundobjects.miz")
result = {}
def append_group(cp_id, category, group_id, object_id, position, heading):
global result
if cp_id not in result:
result[cp_id] = []
result[cp_id].append(TheaterGroundObject(category, cp_id, group_id, object_id, position, heading))
def parse_name(name: str) -> typing.Tuple:
args = str(name).split("|")
if len(args) == 3:
args.append("1")
return args[0], int(args[1]), int(args[2]), int(args[3])
for group in m.country("Russia").static_group + m.country("Russia").vehicle_group:
try:
category, cp_id, group_id, object_id = parse_name(str(group.name))
except:
print("Failed to parse {}".format(group.name))
continue
append_group(cp_id, category, group_id, object_id, [group.position.x, group.position.y], group.units[0].heading)
print("Total {} objects".format(sum([len(x) for x in result.values()])))
with open("../cau_groundobjects.p", "wb") as f:
pickle.dump(result, f)

View File

@ -1,10 +1,14 @@
import os
import dcs
from gen.aircraft import AircraftConflictGenerator
from game import db
from gen.aircraft import AircraftConflictGenerator
dcs.planes.FlyingType.payload_dirs = [os.path.join(os.path.dirname(os.path.realpath(__file__)), "..\\payloads")]
mis = dcs.Mission(dcs.terrain.PersianGulf())
pos = dcs.terrain.PersianGulf().khasab().position
airgen = AircraftConflictGenerator(mis, None)
airgen = AircraftConflictGenerator(mis, None, None)
for t, uts in db.UNIT_BY_TASK.items():
if t != dcs.task.CAP and t != dcs.task.CAS:
@ -25,6 +29,6 @@ for t, uts in db.UNIT_BY_TASK.items():
altitude=10000
)
g.task = t.name
airgen._setup_group(g, t)
airgen._setup_group(g, t, 0)
mis.save("loadout_test.miz")

View File

@ -16,7 +16,7 @@ IGNORED_PATHS = [
"venv",
]
VERSION = "1.3.3"
VERSION = input("version str:")
def _zip_dir(archieve, path):

View File

@ -73,7 +73,10 @@ class CaucasusTheater(ConflictTheater):
self.carrier_1.captured = True
self.soganlug.captured = True
with open("resources/cau_groundobjects.p", "rb") as f:
self.set_groundobject(pickle.load(f))
def add_controlpoint(self, point: ControlPoint, connected_to: typing.Collection[ControlPoint] = []):
point.name = " ".join(re.split(r" |-", point.name)[:1])
point.name = " ".join(re.split(r"[ -]", point.name)[:1])
super(CaucasusTheater, self).add_controlpoint(point, connected_to=connected_to)

View File

@ -6,6 +6,7 @@ from dcs.mapping import Point
from .landmap import ray_tracing
from .controlpoint import ControlPoint
from .theatergroundobject import TheaterGroundObject
SIZE_TINY = 150
SIZE_SMALL = 600
@ -48,6 +49,7 @@ COAST_DR_W = [135, 180, 225, 315]
class ConflictTheater:
terrain = None # type: dcs.terrain.Terrain
controlpoints = None # type: typing.Collection[ControlPoint]
reference_points = None # type: typing.Dict
overview_image = None # type: str
landmap_poly = None
@ -55,6 +57,14 @@ class ConflictTheater:
def __init__(self):
self.controlpoints = []
self.groundobjects = []
def set_groundobject(self, dictionary: typing.Dict[int, typing.Collection[TheaterGroundObject]]):
for id, value in dictionary.items():
for cp in self.controlpoints:
if cp.id == id:
cp.ground_objects = value
break
def add_controlpoint(self, point: ControlPoint, connected_to: typing.Collection[ControlPoint] = []):
for connected_point in connected_to:

View File

@ -5,18 +5,23 @@ from dcs.mapping import *
from dcs.country import *
from dcs.terrain import Airport
from .theatergroundobject import TheaterGroundObject
class ControlPoint:
connected_points = [] # type: typing.List[ControlPoint]
ground_objects = None # type: typing.Collection[TheaterGroundObject]
position = None # type: Point
captured = False
has_frontline = True
id = 0
base = None # type: theater.base.Base
at = None # type: db.StartPosition
def __init__(self, name: str, position: Point, at, radials: typing.Collection[int], size: int, importance: int, has_frontline=True):
def __init__(self, id: int, name: str, position: Point, at, radials: typing.Collection[int], size: int, importance: int, has_frontline=True):
import theater.base
self.id = id
self.name = " ".join(re.split(r" |-", name)[:2])
self.full_name = name
self.position = position
@ -33,12 +38,12 @@ class ControlPoint:
@classmethod
def from_airport(cls, airport: Airport, radials: typing.Collection[int], size: int, importance: int, has_frontline=True):
assert airport
return cls(airport.name, airport.position, airport, radials, size, importance, has_frontline)
return cls(airport.id, airport.name, airport.position, airport, radials, size, importance, has_frontline)
@classmethod
def carrier(cls, name: str, at: Point):
import theater.conflicttheater
return cls(name, at, at, theater.conflicttheater.LAND, theater.conflicttheater.SIZE_SMALL, 1)
return cls(0, name, at, at, theater.conflicttheater.LAND, theater.conflicttheater.SIZE_SMALL, 1)
def __str__(self):
return self.name

View File

@ -0,0 +1,54 @@
import typing
from dcs.mapping import Point
NAME_BY_CATEGORY = {
"power": "Power plant",
"ammo": "Ammo depot",
"fuel": "Fuel depot",
"defense": "AA Defense Site",
"warehouse": "Warehouse",
}
ABBREV_NAME = {
"power": "PLANT",
"ammo": "AMMO",
"fuel": "FUEL",
"defense": "AA",
"warehouse": "WARE",
}
class TheaterGroundObject:
object_id = 0
cp_id = 0
group_id = 0
heading = 0
location = None # type: typing.Collection[int]
category = None # type: str
def __init__(self, category, cp_id, group_id, object_id, location, heading):
self.category = category
self.cp_id = cp_id
self.group_id = group_id
self.object_id = object_id
self.location = location
self.heading = heading
@property
def string_identifier(self):
return "{}|{}|{}|{}".format(self.category, self.cp_id, self.group_id, self.object_id)
@property
def position(self) -> Point:
return Point(*self.location)
@property
def name_abbrev(self) -> str:
return ABBREV_NAME[self.category]
def __str__(self):
return NAME_BY_CATEGORY[self.category]
def matches_string_identifier(self, id):
return self.string_identifier == id

View File

@ -4,25 +4,31 @@ from ui.eventresultsmenu import *
from game import *
from game.event import *
from .styles import STYLES
from .styles import STYLES, RED
UNITTYPES_FOR_EVENTS = {
FrontlineAttackEvent: [CAS, PinpointStrike],
FrontlinePatrolEvent: [CAP, PinpointStrike],
BaseAttackEvent: [CAP, CAS, PinpointStrike],
StrikeEvent: [CAP, CAS],
InterceptEvent: [CAP],
InsurgentAttackEvent: [CAS],
NavalInterceptEvent: [CAS],
AntiAAStrikeEvent: [CAS],
InfantryTransportEvent: [Embarking],
}
AI_BAN_FOR_EVENTS = {
InfantryTransportEvent: [Embarking],
StrikeEvent: [CAS],
}
class EventMenu(Menu):
aircraft_scramble_entries = None # type: typing.Dict[PlaneType , Entry]
aircraft_client_entries = None # type: typing.Dict[PlaneType, Entry]
armor_scramble_entries = None # type: typing.Dict[VehicleType, Entry]
error_label = None # type: Label
awacs = None # type: IntVar
def __init__(self, window: Window, parent, game: Game, event: event.Event):
@ -54,11 +60,14 @@ class EventMenu(Menu):
def label(text, _row=None, _column=None, sticky=None):
nonlocal row
Label(self.frame, text=text, **STYLES["widget"]).grid(row=_row and _row or row, column=_column and _column or 0, sticky=sticky)
new_label = Label(self.frame, text=text, **STYLES["widget"])
new_label.grid(row=_row and _row or row, column=_column and _column or 0, sticky=sticky)
if _row is None:
row += 1
return new_label
def scrable_row(unit_type, unit_count):
nonlocal row
Label(self.frame, text="{} ({})".format(db.unit_type_name(unit_type), unit_count), **STYLES["widget"]).grid(row=row, sticky=W)
@ -138,6 +147,8 @@ class EventMenu(Menu):
row += 1
header("Ready ?")
self.error_label = label("")
self.error_label["fg"] = RED
Button(self.frame, text="Commit", command=self.start, **STYLES["btn-primary"]).grid(column=0, row=row, sticky=E, padx=5, pady=(10,10))
Button(self.frame, text="Back", command=self.dismiss, **STYLES["btn-warning"]).grid(column=3, row=row, sticky=E, padx=5, pady=(10,10))
row += 1
@ -214,6 +225,16 @@ class EventMenu(Menu):
if amount > 0:
scrambled_armor[unit_type] = amount
if type(self.event) in AI_BAN_FOR_EVENTS:
banned_tasks_for_ai = AI_BAN_FOR_EVENTS[type(self.event)]
for task in banned_tasks_for_ai:
scrambled_slots = [1 for x in scrambled_aircraft if db.unit_task(x) == task]
scrambled_client_slots = [1 for x in scrambled_clients if db.unit_task(x) == task]
if scrambled_slots != scrambled_client_slots:
self.error_label["text"] = "AI slots of task {} are not supported for this operation".format(task.name)
return
if type(self.event) is BaseAttackEvent:
e = self.event # type: BaseAttackEvent
if self.game.is_player_attack(self.event):
@ -263,6 +284,14 @@ class EventMenu(Menu):
e.player_attacking(transport=scrambled_aircraft, clients=scrambled_clients)
else:
assert False
elif type(self.event) is StrikeEvent:
e = self.event # type: StrikeEvent
if self.game.is_player_attack(self.event):
e.player_attacking(strikegroup=scrambled_cas,
escort=scrambled_sweep,
clients=scrambled_clients)
else:
assert False
self.game.initiate_event(self.event)
EventResultsMenu(self.window, self.parent, self.game, self.event).display()

View File

@ -54,12 +54,9 @@ class EventResultsMenu(Menu):
pg.start(10)
row += 1
Separator(self.frame, orient=HORIZONTAL).grid(row=row, sticky=EW);
row += 1
Label(self.frame, text="Cheat operation results: ", **STYLES["strong"]).grid(column=0, row=row,
columnspan=2, sticky=NSEW,
pady=5);
pady=5)
row += 1
Button(self.frame, text="full enemy losses", command=self.simulate_result(0, 1),
@ -116,7 +113,7 @@ class EventResultsMenu(Menu):
def simulate_result(self, player_factor: float, enemy_factor: float):
def action():
debriefing = Debriefing({})
debriefing = Debriefing({}, [])
def count(country: Country) -> typing.Dict[UnitType, int]:
result = {}

View File

@ -59,14 +59,59 @@ def parse_mutliplayer_debriefing(contents: str):
class Debriefing:
def __init__(self, dead_units):
def __init__(self, dead_units, dead_objects):
self.destroyed_units = {} # type: typing.Dict[str, typing.Dict[UnitType, int]]
self.alive_units = {} # type: typing.Dict[str, typing.Dict[UnitType, int]]
self.destroyed_objects = [] # type: typing.List[str]
self._dead_units = dead_units
self._dead_objects = dead_objects
@classmethod
def parse(cls, path: str):
dead_units = {}
dead_objects = []
def append_dead_unit(country_id, unit_type):
nonlocal dead_units
if country_id not in dead_units:
dead_units[country_id] = {}
if unit_type not in dead_units[country_id]:
dead_units[country_id][unit_type] = 0
dead_units[country_id][unit_type] += 1
def append_dead_object(object_mission_id_str):
nonlocal dead_objects
object_mission_id = int(object_mission_id_str)
if object_mission_id in dead_objects:
logging.info("debriefing: failed to append_dead_object {}: already exists!".format(object_mission_id))
return
dead_objects.append(object_mission_id)
def parse_dead_unit(event):
try:
components = event["initiator"].split("|")
category, country_id, group_id, unit_type = components[0], int(components[1]), int(components[2]), db.unit_type_from_name(components[3])
if unit_type is None:
logging.info("Skipped {} due to no unit type".format(event))
return
if category == "unit":
append_dead_unit(country_id, unit_type)
else:
logging.info("Skipped {} due to category".format(event))
except Exception as e:
logging.error(e)
def parse_dead_object(event):
try:
append_dead_object(event["initiatorMissionID"])
except Exception as e:
logging.error(e)
with open(path, "r") as f:
table_string = f.read()
try:
@ -75,36 +120,18 @@ class Debriefing:
table = parse_mutliplayer_debriefing(table_string)
events = table.get("debriefing", {}).get("events", {})
dead_units = {}
for event in events.values():
event_type = event.get("type", None)
if event_type != "crash" and event_type != "dead":
continue
if event_type in ["crash", "dead"]:
object_initiator = event["initiator"] in ["SKLAD_CRUSH", "SKLADCDESTR", "TEC_A_CRUSH", "BAK_CRUSH"]
defense_initiator = event["initiator"].startswith("defense|")
try:
components = event["initiator"].split("|")
category, country_id, group_id, unit_type = components[0], int(components[1]), int(components[2]), db.unit_type_from_name(components[3])
if unit_type is None:
logging.info("Skipped due to no unit type")
continue
if object_initiator or defense_initiator:
parse_dead_object(event)
else:
parse_dead_unit(event)
if category != "unit":
logging.info("Skipped due to category")
continue
except Exception as e:
logging.error(e)
continue
if country_id not in dead_units:
dead_units[country_id] = {}
if unit_type not in dead_units[country_id]:
dead_units[country_id][unit_type] = 0
dead_units[country_id][unit_type] += 1
return Debriefing(dead_units)
return Debriefing(dead_units, dead_objects)
def calculate_units(self, mission: Mission, player_name: str, enemy_name: str):
def count_groups(groups: typing.List[UnitType]) -> typing.Dict[UnitType, int]:
@ -142,6 +169,13 @@ class Debriefing:
enemy.name: {k: v - self.destroyed_units[enemy.name].get(k, 0) for k, v in enemy_units.items()},
}
for mission_id in self._dead_objects:
for group in mission.country(enemy.name).static_group + mission.country(enemy.name).vehicle_group:
if group.id == mission_id:
self.destroyed_objects.append(str(group.name))
self.destroyed_objects += self._dead_defense
def debriefing_directory_location() -> str:
return os.path.join(base_path(), "liberation_debriefings")