Compare commits

..

13 Commits

Author SHA1 Message Date
Khopa
a7824039e3 Added revenue for derrick building 2020-06-07 15:54:15 +02:00
Khopa
bb9247e821 Fix info panel messages. 2020-06-06 16:57:20 +02:00
Khopa
4bc04ec031 Auto redeploy frontline units. 2020-06-06 16:50:19 +02:00
Khopa
8fd91e6c5c Added possible ground target : Oil derrick 2020-06-06 15:45:15 +02:00
Khopa
ee137d086a Change power station template. (Some buildings could superpose in older version) 2020-06-06 15:10:07 +02:00
Khopa
fcd81850cb Debriefing and info on AA site and buildings destroyed. KC130 replace S3B 2020-06-06 04:10:22 +02:00
Khopa
aa4b07d024 Add more waypoints to generated flights 2020-06-05 21:24:23 +02:00
Khopa
16a096d288 Possible to setup whether AI should starts from parking or not. 2020-06-05 14:23:38 +02:00
Khopa
b219b2a71b Possible to setup custom saved game and installation directory. 2020-06-05 14:21:42 +02:00
Khopa
ce70242c35 Added 947 to allies and default payload for P47 2020-06-04 13:30:38 +02:00
Khopa
dec01e2611 Added P-47 to db 2020-06-04 13:25:14 +02:00
Khopa
4373d89661 WIP on CAP, and AI units can starts from ground uncontrolled instead of using late activation. 2020-06-03 00:45:44 +02:00
Khopa
c73290eebb Nav Target point for JF-17 and F-14.
Fix activation trigger using wrong coalition when playing REDFOR side.
2020-06-02 13:00:10 +02:00
52 changed files with 1164 additions and 642 deletions

View File

@@ -1,10 +1,39 @@
#2.0 RC 7
##Features/Improvements :
* **[Units/Factions]** Added P-47D-30 for factions allies_1944
* **[Units/Factions]** Replaced S3-B Tanker by KC130 for most factions
* **[Mission Generator]** AI Flight generator has been reworked
* **[Mission Generator]** Add PP points for JF-17 on STRIKE missions
* **[Mission Generator]** Add ST point for F-14B on STRIKE missions
* **[Mission Generator]** Flights with client slots will never be delayed
* **[Mission Generator]** AI units can start from parking (Added a new setting)
* **[Mission Generator]** Tacan for carrier will only be in Mode X from now
* **[Mission Generator]** RTB waypoints for autogenerated flights
* **[Info Panel]** Added information about destroyed buildings in info panel
* **[Info Panel]** Added information about destroyed units at SAM site in info panel
* **[Info Panel]** Added information about units destroyed outside the frontline in the debriefing window
* **[Info Panel]** Added information about buildings destroyed in the debriefing window
* **[Map]** Tooltip now contains the list of building for Strike targets on the map
* **[Map]** Added "Oil derrick" building
* **[Misc]** Made it possible to setup DCS Saved Games directory and DCS installation directory manually
##Fixed issues :
* **[Mission Generator]** When playing as RED the activation trigger would not be properly generated
* **[Mission Generator]** Changed "strike" payload for Su-24M that was innefective
* **[Mission Generator]** Changed "strike" payload for JF-17 to use LS-6 bombs instead of GBU
* **[Mission Generator]** FW-190A8 is now properly considered as a flyable
* **[Maps/Campaign]** Now using Vasiani airport instead of Soganlung in North Caucasus campaign (More parking slots)
* **[Info Panel]** Message displayed on base capture event stated that the ennemy captured an airbase, while it was the player who captured it.
* **[Map]** Graphical glitch on map when one building of an objective was destroyed, but not the others
* **[Map]** Change power station template. (Buildings could end up superposed).
#2.0 RC 6
Saves file from RC5 are not compatible with the new version.
Sorry :(
##Features/Improvements :
* **[Units/Factions]** Supercarrier support (You have to go to settings to enable it, if you have the supercarrier module)
* **[Units/Factions]** Added 'Modern Bluefor' factions, containing all most popular DCS flyable units
* **[Units/Factions]** Factions US 2005 / 1990 will now sometimes have Arleigh Burke class ships instead of Perry as carrier escorts
@@ -19,7 +48,6 @@ Sorry :(
* **[UX]** : Improved flight selection behaviour in the Mission Planning Window
##Fixed issues :
* **[Mission Generator]** Payloads were not correctly assigned in the release version.
* **[Mission Generator]** Game generation does not work when "no night mission" settings was selected and the current time was "day"
* **[Mission Generator]** Game generation does not work when the player selected faction has no AWACS

View File

@@ -150,6 +150,7 @@ PRICES = {
S_3B_Tanker: 13,
IL_78M: 13,
KC_135: 13,
KC130: 13,
A_50: 8,
E_3A: 8,
@@ -158,6 +159,7 @@ PRICES = {
# WW2
P_51D_30_NA: 6,
P_51D: 6,
P_47D_30: 6,
# armor
Armor.APC_MTLB: 4,
@@ -344,6 +346,7 @@ UNIT_BY_TASK = {
Mi_24V,
MiG_27K,
A_20G,
P_47D_30,
Ju_88A4,
],
Transport: [
@@ -357,6 +360,7 @@ UNIT_BY_TASK = {
Refueling: [
IL_78M,
KC_135,
KC130,
S_3B_Tanker,
],
AWACS: [E_3A, A_50, ],
@@ -793,7 +797,8 @@ TIME_PERIODS = {
REWARDS = {
"power": 4, "warehouse": 2, "fuel": 2, "ammo": 2,
"farp": 1, "fob": 1, "factory": 10, "comms": 10, "oil": 10
"farp": 1, "fob": 1, "factory": 10, "comms": 10, "oil": 10,
"derrick": 8
}
# Base post-turn bonus value
@@ -1029,14 +1034,6 @@ def _validate_db():
assert unit_type not in total_set, "{} is duplicate for task {}".format(unit_type, t)
total_set.add(unit_type)
# check country allegiance
for unit_type in total_set:
did_find = False
for country_units_list in FACTIONS.values():
if unit_type in country_units_list["units"]:
did_find = True
print("WARN : {} not in country list".format(unit_type))
# check prices
for unit_type in total_set:
assert unit_type in PRICES, "{} not in prices".format(unit_type)

View File

@@ -127,17 +127,6 @@ class Event:
self.operation.current_mission.save(persistency.mission_path_for("liberation_nextturn.miz"))
self.environment_settings = self.operation.environment_settings
def generate_quick(self):
pass
# TODO : This is not needed anymore. The player can start mission in flight from the flight planner if he want it to be quick.
# TODO : remove this method
#self.operation.is_awacs_enabled = self.is_awacs_enabled
#self.operation.environment_settings = self.environment_settings
#
#self.operation.prepare(self.game.theater.terrain, is_quick=True)
#self.operation.generate()
#self.operation.current_mission.save(persistency.mission_path_for("liberation_nextturn_quick.miz"))
def commit(self, debriefing: Debriefing):
logging.info("Commiting mission results")
@@ -190,16 +179,30 @@ class Event:
logging.info("cp {} killing ground object {}".format(cp, ground_object.string_identifier))
cp.ground_objects[i].is_dead = True
info = Information("Building destroyed",
ground_object.dcs_identifier + " has been destroyed at location " + ground_object.obj_name,
self.game.turn)
self.game.informations.append(info)
# -- AA Site groups
destroyed_units = 0
info = Information("Units destroyed at " + ground_object.obj_name,
"",
self.game.turn)
for i, ground_object in enumerate(cp.ground_objects):
if ground_object.dcs_identifier in ["AA", "CARRIER", "LHA"]:
for g in ground_object.groups:
for u in g.units:
if u.name == destroyed_ground_unit_name:
g.units.remove(u)
destroyed_units = destroyed_units + 1
info.text = u.type
ucount = sum([len(g.units) for g in ground_object.groups])
if ucount == 0:
ground_object.is_dead = True
if destroyed_units > 0:
self.game.informations.append(info)
# ------------------------------
# Captured bases
@@ -213,28 +216,27 @@ class Event:
id = int(captured.split("||")[0])
new_owner_coalition = int(captured.split("||")[1])
captured_cps = []
for cp in self.game.theater.controlpoints:
if cp.id == id:
pname = ""
if cp.captured and new_owner_coalition != coalition:
cp.captured = False
info = Information(cp.name + " lost !",
"The ennemy took control of " + cp.name + "\nShame on us !",
self.game.turn)
info = Information(cp.name + " lost !", "The ennemy took control of " + cp.name + "\nShame on us !", self.game.turn)
self.game.informations.append(info)
pname = self.game.enemy_name
captured_cps.append(cp)
elif not(cp.captured) and new_owner_coalition == coalition:
cp.captured = True
info = Information(cp.name + " captured !", "The ennemy took control of " + cp.name + "\nShame on us !", self.game.turn)
info = Information(cp.name + " captured !", "We took control of " + cp.name + "! Great job !", self.game.turn)
self.game.informations.append(info)
pname = self.game.player_name
captured_cps.append(cp)
else:
continue
cp.base.aircraft = {}
cp.base.armor = {}
cp.base.aa = {}
airbase_def_id = 0
for g in cp.ground_objects:
@@ -243,6 +245,10 @@ class Event:
generate_airbase_defense_group(airbase_def_id, g, pname, self.game, cp)
airbase_def_id = airbase_def_id + 1
for cp in captured_cps:
logging.info("Will run redeploy for " + cp.name)
self.redeploy_units(cp)
except Exception as e:
print(e)
@@ -321,6 +327,47 @@ class Event:
def skip(self):
pass
def redeploy_units(self, cp):
""""
Auto redeploy units to newly captured base
"""
ally_connected_cps = [ocp for ocp in cp.connected_points if cp.captured == ocp.captured]
enemy_connected_cps = [ocp for ocp in cp.connected_points if cp.captured != ocp.captured]
# If the newly captured cp does not have enemy connected cp,
# then it is not necessary to redeploy frontline units there.
if len(enemy_connected_cps) == 0:
return
else:
# From each ally cp, send reinforcements
for ally_cp in ally_connected_cps:
total_units_redeployed = 0
own_enemy_cp = [ocp for ocp in ally_cp.connected_points if ally_cp.captured != ocp.captured]
moved_units = {}
# If the connected base, does not have any more enemy cp connected.
# Or if it is not the opponent redeploying forces there (enemy AI will never redeploy all their forces at once)
if len(own_enemy_cp) > 0 or not cp.captured:
for frontline_unit, count in ally_cp.base.armor.items():
moved_units[frontline_unit] = int(count/2)
total_units_redeployed = total_units_redeployed + int(count/2)
else: # So if the old base, does not have any more enemy cp connected, or if it is an enemy base
for frontline_unit, count in ally_cp.base.armor.items():
moved_units[frontline_unit] = count
total_units_redeployed = total_units_redeployed + count
cp.base.commision_units(moved_units)
ally_cp.base.commit_losses(moved_units)
if total_units_redeployed > 0:
info = Information("Units redeployed", "", self.game.turn)
info.text = str(total_units_redeployed) + " units have been redeployed from " + ally_cp.name + " to " + cp.name
self.game.informations.append(info)
logging.info(info.text)
class UnitsDeliveryEvent(Event):
informational = True

View File

@@ -24,7 +24,7 @@ BLUEFOR_MODERN = {
AJS37,
KC_135,
S_3B_Tanker,
KC130,
C_130,
E_3A,

View File

@@ -11,7 +11,7 @@ France_1995 = {
Mirage_2000_5,
KC_135,
S_3B_Tanker,
KC130,
C_130,
E_3A,

View File

@@ -12,7 +12,7 @@ France_2005 = {
FA_18C_hornet, # Standing as Rafale M
KC_135,
S_3B_Tanker,
KC130,
C_130,
E_3A,

View File

@@ -12,7 +12,7 @@ Germany_1990 = {
F_4E,
KC_135,
S_3B_Tanker,
KC130,
C_130,
E_3A,

View File

@@ -15,7 +15,7 @@ India_2010 = {
Su_30,
KC_135,
S_3B_Tanker,
KC130,
C_130,
E_3A,

View File

@@ -12,7 +12,7 @@ Israel_2000 = {
F_4E,
KC_135,
S_3B_Tanker,
KC130,
C_130,
E_3A,

View File

@@ -11,7 +11,7 @@ Netherlands_1990 = {
F_5E_3,
KC_135,
S_3B_Tanker,
KC130,
C_130,
E_3A,

View File

@@ -12,7 +12,7 @@ Spain_1990 = {
C_101CC,
KC_135,
S_3B_Tanker,
KC130,
C_130,
E_3A,

View File

@@ -11,7 +11,7 @@ Turkey_2005 = {
F_4E,
KC_135,
S_3B_Tanker,
KC130,
C_130,
E_3A,

View File

@@ -12,7 +12,7 @@ UAE_2005 = {
F_16C_50,
KC_135,
S_3B_Tanker,
KC130,
C_130,
E_3A,

View File

@@ -12,7 +12,7 @@ UnitedKingdom_1990 = {
F_4E,
KC_135,
S_3B_Tanker,
KC130,
C_130,
E_3A,

View File

@@ -8,6 +8,7 @@ USA_1944 = {
"units": [
P_51D,
P_51D_30_NA,
P_47D_30,
SpitfireLFMkIX,
SpitfireLFMkIXCW,
A_20G,

View File

@@ -11,7 +11,7 @@ USA_1955 = {
P_51D,
KC_135,
S_3B_Tanker,
KC130,
C_130,
E_3A,

View File

@@ -11,7 +11,7 @@ USA_1960 = {
P_51D,
KC_135,
S_3B_Tanker,
KC130,
C_130,
E_3A,

View File

@@ -12,7 +12,7 @@ USA_1965 = {
F_4E,
KC_135,
S_3B_Tanker,
KC130,
C_130,
E_3A,

View File

@@ -17,7 +17,7 @@ USA_1990 = {
B_1B,
KC_135,
S_3B_Tanker,
KC130,
C_130,
E_3A,

View File

@@ -12,12 +12,11 @@ USA_2005 = {
FA_18C_hornet,
F_16C_50,
JF_17,
A_10C,
AV8BNA,
KC_135,
S_3B_Tanker,
KC130,
C_130,
E_3A,

View File

@@ -145,11 +145,8 @@ class Game:
def initiate_event(self, event: Event):
assert event in self.events
logging.info("Generating {} (regular)".format(event))
event.generate()
logging.info("Generating {} (quick)".format(event))
event.generate_quick()
def finish_event(self, event: Event, debriefing: Debriefing):
logging.info("Finishing event {}".format(event))
@@ -168,6 +165,18 @@ class Game:
else:
return event.name == self.player_name
def get_player_coalition_id(self):
if self.player_country in db.BLUEFOR_FACTIONS:
return 2
else:
return 1
def get_enemy_coalition_id(self):
if self.get_player_coalition_id() == 1:
return 2
else:
return 1
def pass_turn(self, no_action=False, ignored_cps: typing.Collection[ControlPoint] = None):
logging.info("Pass turn")
self.informations.append(Information("End of turn #" + str(self.turn), "-" * 40, 0))

View File

@@ -47,11 +47,9 @@ class GameStats:
for cp in game.theater.controlpoints:
if cp.captured:
turn_data.allied_units.aircraft_count += sum(cp.base.aircraft.values())
turn_data.allied_units.sam_count += sum(cp.base.aa.values())
turn_data.allied_units.vehicles_count += sum(cp.base.armor.values())
else:
turn_data.enemy_units.aircraft_count += sum(cp.base.aircraft.values())
turn_data.enemy_units.sam_count += sum(cp.base.aa.values())
turn_data.enemy_units.vehicles_count += sum(cp.base.armor.values())
self.data_per_turn.append(turn_data)

View File

@@ -21,5 +21,6 @@ class Settings:
perf_artillery = True
perf_moving_units = True
perf_infantry = True
perf_ai_parking_start = True

View File

@@ -1,12 +1,12 @@
from dcs.action import ActivateGroup
from dcs.action import ActivateGroup, AITaskPush
from dcs.condition import TimeAfter, CoalitionHasAirdrome
from dcs.helicopters import UH_1H
from dcs.terrain.terrain import NoParkingSlotError
from dcs.triggers import TriggerOnce, Event
from game.settings import Settings
from gen.flights.ai_flight_planner import FlightPlanner
from gen.flights.flight import Flight, FlightType
from gen.flights.ai_flight_planner import FlightPlanner, CAP_DEFAULT_ENGAGE_DISTANCE, nm_to_meter
from gen.flights.flight import Flight, FlightType, FlightWaypointType
from .conflictgen import *
from .naming import *
from .triggergen import TRIGGER_WAYPOINT_OFFSET
@@ -51,33 +51,6 @@ class AircraftConflictGenerator:
def _start_type(self) -> StartType:
return self.settings.cold_start and StartType.Cold or StartType.Warm
def _group_point(self, point) -> Point:
distance = randint(
int(self.conflict.size * SPREAD_DISTANCE_FACTOR[0]),
int(self.conflict.size * SPREAD_DISTANCE_FACTOR[1]),
)
return point.random_point_within(distance, self.conflict.size * SPREAD_DISTANCE_FACTOR[0])
def _split_to_groups(self, dict: db.PlaneDict, clients: db.PlaneDict = None) -> typing.Collection[typing.Tuple[FlyingType, int, int]]:
for flying_type, count in dict.items():
if clients:
client_count = clients.get(flying_type, 0)
else:
client_count = 0
if flying_type == F_14B:
# workaround since 2 and 3 tomcat collide on carrier
group_size = 2
else:
group_size = 4
while count > 0:
group_size = min(count, group_size)
client_size = max(min(client_count, group_size), 0)
yield (flying_type, group_size, client_size)
count -= group_size
client_count -= client_size
def _setup_group(self, group: FlyingGroup, for_task: typing.Type[Task], client_count: int):
did_load_loadout = False
@@ -251,37 +224,6 @@ class AircraftConflictGenerator:
else:
assert False
def _generate_escort(self, side: Country, units: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition, cp, is_quick=False, should_orbit=False):
groups = []
for flying_type, count, client_count in self._split_to_groups(units, clients):
group = self._generate_group(
name=namegen.next_unit_name(side, cp.id, flying_type),
side=side,
unit_type=flying_type,
count=count,
client_count=client_count,
at=at)
group.task = Escort.name
self._setup_group(group, CAP, client_count)
for escorted_group, waypoint_index in self.escort_targets:
waypoint_index += 1
if not is_quick:
waypoint_index += TRIGGER_WAYPOINT_OFFSET
group.points[0].tasks.append(EscortTaskAction(escorted_group.id, engagement_max_dist=ESCORT_ENGAGEMENT_MAX_DIST, lastwpt=waypoint_index))
if should_orbit:
orbit_task = ControlledTask(OrbitAction(ATTACK_CIRCLE_ALT, pattern=OrbitAction.OrbitPattern.Circle))
orbit_task.stop_after_duration(ATTACK_CIRCLE_DURATION * 60)
orbit_waypoint = self._add_radio_waypoint(group, self.conflict.position, CAS_ALTITUDE)
orbit_waypoint.tasks.append(orbit_task)
orbit_waypoint.tasks.append(EngageTargets(max_distance=DEFENCE_ENGAGEMENT_MAX_DISTANCE))
groups.append(group)
return groups
def _setup_custom_payload(self, flight, group:FlyingGroup):
if flight.use_custom_loadout:
@@ -303,74 +245,56 @@ class AircraftConflictGenerator:
def generate_flights(self, cp, country, flight_planner:FlightPlanner):
for flight in flight_planner.interceptor_flights:
group = self.generate_planned_flight(cp, country, flight)
self.setup_group_as_intercept_flight(group, flight)
self._setup_custom_payload(flight, group)
self.setup_group_activation_trigger(flight, group)
for flight in flight_planner.cap_flights:
group = self.generate_planned_flight(cp, country, flight)
self.setup_group_as_cap_flight(group, flight)
self._setup_custom_payload(flight, group)
self.setup_group_activation_trigger(flight, group)
for flight in flight_planner.cas_flights:
group = self.generate_planned_flight(cp, country, flight)
self.setup_group_as_cas_flight(group, flight)
self._setup_custom_payload(flight, group)
self.setup_group_activation_trigger(flight, group)
for flight in flight_planner.sead_flights:
group = self.generate_planned_flight(cp, country, flight)
self.setup_group_as_sead_flight(group, flight)
self._setup_custom_payload(flight, group)
self.setup_group_activation_trigger(flight, group)
for flight in flight_planner.strike_flights:
group = self.generate_planned_flight(cp, country, flight)
self.setup_group_as_strike_flight(group, flight)
self._setup_custom_payload(flight, group)
self.setup_group_activation_trigger(flight, group)
for flight in flight_planner.custom_flights:
for flight in flight_planner.flights:
group = self.generate_planned_flight(cp, country, flight)
if flight.flight_type == FlightType.INTERCEPTION:
self.setup_group_as_intercept_flight(group, flight)
elif flight.flight_type in [FlightType.CAP, FlightType.TARCAP, FlightType.BARCAP]:
self.setup_group_as_cap_flight(group, flight)
elif flight.flight_type in [FlightType.CAS, FlightType.BAI]:
self.setup_group_as_cas_flight(group, flight)
elif flight.flight_type in [FlightType.STRIKE]:
self.setup_group_as_strike_flight(group, flight)
elif flight.flight_type in [FlightType.ANTISHIP]:
self.setup_group_as_antiship_flight(group, flight)
elif flight.flight_type in [FlightType.SEAD, FlightType.DEAD]:
self.setup_group_as_sead_flight(group, flight)
self._setup_custom_payload(flight, group)
else:
self.setup_group_as_cap_flight(group, flight)
self._setup_custom_payload(flight, group)
self.setup_flight_group(group, flight, flight.flight_type)
self.setup_group_activation_trigger(flight, group)
def setup_group_activation_trigger(self, flight, group):
if flight.scheduled_in > 0:
group.late_activation = True
activation_trigger = TriggerOnce(Event.NoEvent, "LiberationActivationTriggerForGroup" + str(group.id))
activation_trigger.add_condition(TimeAfter(seconds=flight.scheduled_in*60))
if flight.scheduled_in > 0 and flight.client_count == 0:
if(flight.from_cp.cptype == ControlPointType.AIRBASE):
if not flight.from_cp.captured:
activation_trigger.add_condition(CoalitionHasAirdrome(1, flight.from_cp.id))
else:
activation_trigger.add_condition(CoalitionHasAirdrome(2, flight.from_cp.id))
if flight.start_type != "In Flight":
group.late_activation = False
group.uncontrolled = True
activation_trigger = TriggerOnce(Event.NoEvent, "LiberationControlTriggerForGroup" + str(group.id))
activation_trigger.add_condition(TimeAfter(seconds=flight.scheduled_in * 60))
if (flight.from_cp.cptype == ControlPointType.AIRBASE):
if flight.from_cp.captured:
activation_trigger.add_condition(
CoalitionHasAirdrome(self.game.get_player_coalition_id(), flight.from_cp.id))
else:
activation_trigger.add_condition(
CoalitionHasAirdrome(self.game.get_enemy_coalition_id(), flight.from_cp.id))
activation_trigger.add_action(ActivateGroup(group.id))
self.m.triggerrules.triggers.append(activation_trigger)
group.add_trigger_action(StartCommand())
activation_trigger.add_action(AITaskPush(group.id, len(group.tasks)))
self.m.triggerrules.triggers.append(activation_trigger)
else:
group.late_activation = True
activation_trigger = TriggerOnce(Event.NoEvent, "LiberationActivationTriggerForGroup" + str(group.id))
activation_trigger.add_condition(TimeAfter(seconds=flight.scheduled_in*60))
if(flight.from_cp.cptype == ControlPointType.AIRBASE):
if flight.from_cp.captured:
activation_trigger.add_condition(CoalitionHasAirdrome(self.game.get_player_coalition_id(), flight.from_cp.id))
else:
activation_trigger.add_condition(CoalitionHasAirdrome(self.game.get_enemy_coalition_id(), flight.from_cp.id))
activation_trigger.add_action(ActivateGroup(group.id))
self.m.triggerrules.triggers.append(activation_trigger)
def generate_planned_flight(self, cp, country, flight:Flight):
try:
if flight.start_type == "In Flight" or flight.client_count == 0:
if flight.client_count == 0 and self.game.settings.perf_ai_parking_start:
flight.start_type = "Warm"
if flight.start_type == "In Flight":
group = self._generate_group(
name=namegen.next_unit_name(country, cp.id, flight.unit_type),
side=country,
@@ -379,7 +303,6 @@ class AircraftConflictGenerator:
client_count=0,
at=cp.position)
else:
st = StartType.Runway
if flight.start_type == "Cold":
st = StartType.Cold
@@ -406,6 +329,7 @@ class AircraftConflictGenerator:
start_type=st)
except Exception:
# Generated when there is no place on Runway or on Parking Slots
flight.start_type = "In Flight"
group = self._generate_group(
name=namegen.next_unit_name(country, cp.id, flight.unit_type),
side=country,
@@ -425,65 +349,74 @@ class AircraftConflictGenerator:
group.add_waypoint(Point(point.x,point.y), point.alt)
def setup_group_as_cap_flight(self, group, flight):
self._setup_group(group, CAP, flight.client_count)
for point in flight.points:
group.add_waypoint(Point(point.x,point.y), point.alt)
def setup_flight_group(self, group, flight, flight_type):
def setup_group_as_cas_flight(self, group, flight):
group.task = CAS.name
self._setup_group(group, CAS, flight.client_count)
if flight_type in [FlightType.CAP, FlightType.BARCAP, FlightType.TARCAP]:
group.task = CAP.name
self._setup_group(group, CAP, flight.client_count)
# group.points[0].tasks.clear()
# group.tasks.clear()
# group.tasks.append(EngageTargets(max_distance=40, targets=[Targets.All.Air]))
# group.tasks.append(EngageTargets(max_distance=nm_to_meter(120), targets=[Targets.All.Air]))
pass
elif flight_type in [FlightType.CAS, FlightType.BAI]:
group.task = CAS.name
self._setup_group(group, CAS, flight.client_count)
group.points[0].tasks.clear()
group.points[0].tasks.append(CASTaskAction())
group.points[0].tasks.append(OptReactOnThreat(OptReactOnThreat.Values.EvadeFire))
group.points[0].tasks.append(OptROE(OptROE.Values.OpenFireWeaponFree))
elif flight_type in [FlightType.SEAD, FlightType.DEAD]:
group.task = SEAD.name
self._setup_group(group, SEAD, flight.client_count)
group.points[0].tasks.clear()
group.points[0].tasks.append(SEADTaskAction())
group.points[0].tasks.append(OptReactOnThreat(OptReactOnThreat.Values.EvadeFire))
group.points[0].tasks.append(OptROE(OptROE.Values.OpenFireWeaponFree))
group.points[0].tasks.append(OptRestrictJettison(True))
elif flight_type in [FlightType.STRIKE]:
group.task = PinpointStrike.name
self._setup_group(group, GroundAttack, flight.client_count)
group.points[0].tasks.clear()
group.points[0].tasks.append(OptReactOnThreat(OptReactOnThreat.Values.EvadeFire))
group.points[0].tasks.append(OptROE(OptROE.Values.OpenFire))
group.points[0].tasks.append(OptRestrictJettison(True))
elif flight_type in [FlightType.ANTISHIP]:
group.task = AntishipStrike.name
self._setup_group(group, AntishipStrike, flight.client_count)
group.points[0].tasks.clear()
group.points[0].tasks.append(OptReactOnThreat(OptReactOnThreat.Values.EvadeFire))
group.points[0].tasks.append(OptROE(OptROE.Values.OpenFire))
group.points[0].tasks.append(OptRestrictJettison(True))
group.points[0].tasks.clear()
group.points[0].tasks.append(CASTaskAction())
group.points[0].tasks.append(OptReactOnThreat(OptReactOnThreat.Values.EvadeFire))
group.points[0].tasks.append(OptROE(OptROE.Values.OpenFireWeaponFree))
#group.points[0].tasks.append(OptRestrictJettison(True))
for i, point in enumerate(flight.points):
if not point.only_for_player or (point.only_for_player and flight.client_count > 0):
pt = group.add_waypoint(Point(point.x, point.y), point.alt)
if point.waypoint_type == FlightWaypointType.PATROL_TRACK:
action = OrbitAction(altitude=pt.alt, pattern=OrbitAction.OrbitPattern.RaceTrack)
pt.tasks.append(action)
#for tgt in point.targets:
# if hasattr(tgt, "position"):
# engagetgt = EngageTargetsInZone(tgt.position, radius=CAP_DEFAULT_ENGAGE_DISTANCE, targets=[Targets.All.Air])
# pt.tasks.append(engagetgt)
elif point.waypoint_type == FlightWaypointType.LANDING_POINT:
pt.type = "Land"
elif point.waypoint_type == FlightWaypointType.INGRESS_STRIKE:
print("TGTS :")
print(point.targets)
for j, t in enumerate(point.targets):
print(t.position)
pt.tasks.append(Bombing(t.position))
if group.units[0].unit_type == JF_17 and j < 4:
group.add_nav_target_point(t.position, "PP" + str(j + 1))
if group.units[0].unit_type == F_14B and j == 0:
group.add_nav_target_point(t.position, "ST")
for point in flight.points:
group.add_waypoint(Point(point.x,point.y), point.alt)
if pt is not None:
pt.alt_type = point.alt_type
pt.name = String(point.name)
def setup_group_as_sead_flight(self, group, flight):
group.task = SEAD.name
self._setup_group(group, SEAD, flight.client_count)
group.points[0].tasks.clear()
group.points[0].tasks.append(SEADTaskAction())
group.points[0].tasks.append(OptReactOnThreat(OptReactOnThreat.Values.EvadeFire))
group.points[0].tasks.append(OptROE(OptROE.Values.OpenFireWeaponFree))
group.points[0].tasks.append(OptRestrictJettison(True))
i = 1
for point in flight.points:
group.add_waypoint(Point(point.x,point.y), point.alt)
group.points[i].tasks.clear()
group.points[i].tasks.append(SEADTaskAction())
i = i + 1
def setup_group_as_strike_flight(self, group, flight):
group.task = PinpointStrike.name
self._setup_group(group, GroundAttack, flight.client_count)
group.points[0].tasks.clear()
group.points[0].tasks.append(CASTaskAction())
group.points[0].tasks.append(OptReactOnThreat(OptReactOnThreat.Values.EvadeFire))
group.points[0].tasks.append(OptROE(OptROE.Values.OpenFire))
group.points[0].tasks.append(OptRestrictJettison(True))
i = 1
bombing_point_found = False
for point in flight.points:
group.add_waypoint(Point(point.x,point.y), point.alt)
if not bombing_point_found:
for t in point.targets:
if hasattr(t, "obj_name"):
buildings = self.game.theater.find_ground_objects_by_obj_name(t.obj_name)
for building in buildings:
group.points[i].tasks.append(Bombing(building.position))
else:
group.points[i].tasks.append(Bombing(t.position))
bombing_point_found = True
i = i + 1
self._setup_custom_payload(flight, group)
def setup_group_as_antiship_flight(self, group, flight):

View File

@@ -5,8 +5,25 @@ import random
from game import db
from gen import Conflict
from gen.flights.ai_flight_planner_db import INTERCEPT_CAPABLE, CAP_CAPABLE, CAS_CAPABLE, SEAD_CAPABLE
from gen.flights.flight import Flight, FlightType, FlightWaypoint
from gen.flights.ai_flight_planner_db import INTERCEPT_CAPABLE, CAP_CAPABLE, CAS_CAPABLE, SEAD_CAPABLE, STRIKE_CAPABLE
from gen.flights.flight import Flight, FlightType, FlightWaypoint, FlightWaypointType
def meter_to_feet(value_in_meter):
return int(3.28084 * value_in_meter)
def feet_to_meter(value_in_feet):
return int(float(value_in_feet)/3.048)
def meter_to_nm(value_in_meter):
return int(float(value_in_meter)*0.000539957)
def nm_to_meter(value_in_nm):
return int(float(value_in_nm)*1852)
# TODO : Ideally should be based on the aircraft type instead / Availability of fuel
STRIKE_MAX_RANGE = 1500000
@@ -19,11 +36,15 @@ CAS_EVERY_X_MINUTES = 30
SEAD_EVERY_X_MINUTES = 40
STRIKE_EVERY_X_MINUTES = 40
INGRESS_EGRESS_DISTANCE = 45000
INGRESS_ALT = 6096 # 20k feet
EGRESS_ALT = 6096 # 20k feet
PATROL_ALT_RANGE = (3600, 9200)
INGRESS_EGRESS_DISTANCE = nm_to_meter(45)
INGRESS_ALT = feet_to_meter(20000)
EGRESS_ALT = feet_to_meter(20000)
PATROL_ALT_RANGE = (feet_to_meter(15000), feet_to_meter(33000))
NAV_ALT = 9144
PATTERN_ALTITUDE = feet_to_meter(5000)
CAP_DEFAULT_ENGAGE_DISTANCE = nm_to_meter(40)
class FlightPlanner:
@@ -69,7 +90,7 @@ class FlightPlanner:
self.commision_strike()
# TODO : commision STRIKE / ANTISHIP
# TODO : commision ANTISHIP
def remove_flight(self, index):
try:
@@ -135,30 +156,64 @@ class FlightPlanner:
ftype = FlightType.BARCAP if self.from_cp.is_carrier else FlightType.CAP
flight = Flight(unit, 2, self.from_cp, ftype)
# Flight path : fly over each ground object (TODO : improve)
flight.points = []
flight.scheduled_in = offset + i*random.randint(CAP_EVERY_X_MINUTES-5, CAP_EVERY_X_MINUTES+5)
patrol_alt = random.randint(PATROL_ALT_RANGE[0], PATROL_ALT_RANGE[1])
patrolled = []
for ground_object in self.from_cp.ground_objects:
if ground_object.group_id not in patrolled and not ground_object.airbase_group:
point = FlightWaypoint(ground_object.position.x, ground_object.position.y, patrol_alt)
point.name = "Patrol point"
point.description = "Patrol #" + str(len(flight.points))
point.pretty_name = "Patrol #" + str(len(flight.points))
flight.points.append(point)
patrolled.append(ground_object.group_id)
# Choose a location for CAP patrols (Either behind frontline if there is one, or to protect ground objects)
if len(self._get_cas_locations()) > 0:
loc = random.choice(self._get_cas_locations())
ingress, heading, distance = Conflict.frontline_vector(self.from_cp, loc, self.game.theater)
center = ingress.point_from_heading(heading, distance / 2)
orbit_center = center.point_from_heading(heading - 90, random.randint(nm_to_meter(6), nm_to_meter(15)))
radius = distance * 2
orbit0p = orbit_center.point_from_heading(heading, radius)
orbit1p = orbit_center.point_from_heading(heading + 180, radius)
elif len(self.from_cp.ground_objects) > 0:
loc = random.choice(self.from_cp.ground_objects)
hdg = self.from_cp.position.heading_between_point(loc.position)
radius = random.randint(nm_to_meter(5), nm_to_meter(10))
orbit0p = loc.position.point_from_heading(hdg - 90, radius)
orbit1p = loc.position.point_from_heading(hdg + 90, radius)
else:
loc = self.from_cp.position.point_from_heading(random.randint(0, 360), random.randint(nm_to_meter(5), nm_to_meter(40)))
hdg = self.from_cp.position.heading_between_point(loc.position)
radius = random.randint(nm_to_meter(40), nm_to_meter(120))
orbit0p = loc.position.point_from_heading(hdg - 90, radius)
orbit1p = loc.position.point_from_heading(hdg + 90, radius)
if len(flight.points) == 0:
for i in range(3):
pos = self.from_cp.position.point_from_heading(random.randint(0, 360), random.randint(30000, 80000))
point = FlightWaypoint(pos.x, pos.y, patrol_alt)
point.name = "Patrol point"
point.description = "Patrol #" + str(len(flight.points))
point.pretty_name = "Patrol #" + str(len(flight.points))
flight.points.append(point)
# Create points
ascend = self.generate_ascend_point(self.from_cp)
flight.points.append(ascend)
orbit0 = FlightWaypoint(orbit0p.x, orbit0p.y, patrol_alt)
orbit0.name = "ORBIT 0"
orbit0.description = "Standby between this point and the next one"
orbit0.pretty_name = "Orbit race-track start"
orbit0.waypoint_type = FlightWaypointType.PATROL_TRACK
flight.points.append(orbit0)
orbit1 = FlightWaypoint(orbit1p.x, orbit1p.y, patrol_alt)
orbit1.name = "ORBIT 1"
orbit1.description = "Standby between this point and the previous one"
orbit1.pretty_name = "Orbit race-track end"
orbit1.waypoint_type = FlightWaypointType.PATROL
flight.points.append(orbit1)
orbit0.targets.append(self.from_cp)
obj_added = []
for ground_object in self.from_cp.ground_objects:
if ground_object.obj_name not in obj_added and not ground_object.airbase_group:
orbit0.targets.append(ground_object)
obj_added.append(ground_object.obj_name)
descend = self.generate_descend_point(self.from_cp)
flight.points.append(descend)
rtb = self.generate_rtb_waypoint(self.from_cp)
flight.points.append(rtb)
self.cap_flights.append(flight)
self.flights.append(flight)
@@ -197,26 +252,39 @@ class FlightPlanner:
center = ingress.point_from_heading(heading, distance/2)
egress = ingress.point_from_heading(heading, distance)
flight.targets.append(center)
ascend = self.generate_ascend_point(self.from_cp)
flight.points.append(ascend)
ingress_point = FlightWaypoint(ingress.x, ingress.y, 1000)
ingress_point.alt_type = "RADIO"
ingress_point.name = "INGRESS"
ingress_point.pretty_name = "INGRESS"
ingress_point.description = "Ingress into CAS area"
ingress_point.waypoint_type = FlightWaypointType.INGRESS_CAS
flight.points.append(ingress_point)
center_point = FlightWaypoint(center.x, center.y, 1000)
center_point.alt_type = "RADIO"
center_point.description = "Provide CAS"
center_point.name = "CAS"
center_point.pretty_name = "INGRESS"
center_point.pretty_name = "CAS"
center_point.waypoint_type = FlightWaypointType.CAS
flight.points.append(center_point)
egress_point = FlightWaypoint(egress.x, egress.y, 1000)
egress_point.alt_type = "RADIO"
egress_point.description = "Egress from CAS area"
egress_point.name = "EGRESS"
egress_point.pretty_name = "EGRESS"
egress_point.waypoint_type = FlightWaypointType.EGRESS
flight.points.append(egress_point)
descend = self.generate_descend_point(self.from_cp)
flight.points.append(descend)
rtb = self.generate_rtb_waypoint(self.from_cp)
flight.points.append(rtb)
self.cas_flights.append(flight)
self.flights.append(flight)
@@ -251,6 +319,9 @@ class FlightPlanner:
flight.points = []
flight.scheduled_in = offset + i*random.randint(SEAD_EVERY_X_MINUTES-5, SEAD_EVERY_X_MINUTES+5)
ascend = self.generate_ascend_point(self.from_cp)
flight.points.append(ascend)
location = self.potential_sead_targets[0][0]
self.potential_sead_targets.pop(0)
@@ -262,25 +333,36 @@ class FlightPlanner:
ingress_point = FlightWaypoint(ingress_pos.x, ingress_pos.y, INGRESS_ALT)
ingress_point.pretty_name = "INGRESS on " + location.obj_name
ingress_point.description = "INGRESS on " + location.obj_name
ingress_point.waypoint_type = FlightWaypointType.INGRESS_SEAD
flight.points.append(ingress_point)
point = FlightWaypoint(location.position.x, location.position.y, 1000)
point = FlightWaypoint(location.position.x, location.position.y, 0)
point.alt_type = "RADIO"
if flight.flight_type == FlightType.DEAD:
point.description = "SEAD on " + location.obj_name
point.pretty_name = "SEAD on " + location.obj_name
point.only_for_player = True
else:
point.description = "DEAD on " + location.obj_name
point.pretty_name = "DEAD on " + location.obj_name
point.only_for_player = True
point.targets.append(location)
ingress_point.targets.append(location)
flight.points.append(point)
egress_pos = location.position.point_from_heading(egress_heading, INGRESS_EGRESS_DISTANCE)
egress_point = FlightWaypoint(egress_pos.x, egress_pos.y, EGRESS_ALT)
egress_point.pretty_name = "EGRESS on " + location.obj_name
egress_point.description = "EGRESS on " + location.obj_name
egress_point.pretty_name = "EGRESS from " + location.obj_name
egress_point.description = "EGRESS from " + location.obj_name
egress_point.waypoint_type = FlightWaypointType.EGRESS
flight.points.append(egress_point)
descend = self.generate_descend_point(self.from_cp)
flight.points.append(descend)
rtb = self.generate_rtb_waypoint(self.from_cp)
flight.points.append(rtb)
self.sead_flights.append(flight)
self.flights.append(flight)
@@ -293,7 +375,7 @@ class FlightPlanner:
"""
Pick some aircraft to assign them to STRIKE tasks
"""
possible_aircraft = [k for k, v in self.aircraft_inventory.items() if k in CAS_CAPABLE and v >= 2]
possible_aircraft = [k for k, v in self.aircraft_inventory.items() if k in STRIKE_CAPABLE and v >= 2]
inventory = dict({k: v for k, v in self.aircraft_inventory.items() if k in possible_aircraft})
if len(self.potential_strike_targets) > 0:
@@ -315,6 +397,9 @@ class FlightPlanner:
flight.points = []
flight.scheduled_in = offset + i*random.randint(SEAD_EVERY_X_MINUTES-5, SEAD_EVERY_X_MINUTES+5)
ascend = self.generate_ascend_point(self.from_cp)
flight.points.append(ascend)
location = self.potential_strike_targets[0][0]
self.potential_strike_targets.pop(0)
@@ -326,29 +411,62 @@ class FlightPlanner:
ingress_point = FlightWaypoint(ingress_pos.x, ingress_pos.y, INGRESS_ALT)
ingress_point.pretty_name = "INGRESS on " + location.obj_name
ingress_point.description = "INGRESS on " + location.obj_name
ingress_point.name = "INGRESS"
ingress_point.waypoint_type = FlightWaypointType.INGRESS_STRIKE
flight.points.append(ingress_point)
if len(location.groups) > 0:
if len(location.groups) > 0 and location.dcs_identifier == "AA":
for g in location.groups:
for j, u in enumerate(g.units):
point = FlightWaypoint(u.position.x, u.position.y, 0)
point.description = "STRIKE " + "[" + str(location.obj_name) + "] : " + u.type + " #" + str(j)
point.pretty_name = "STRIKE " + "[" + str(location.obj_name) + "] : " + u.type + " #" + str(j)
point.name = location.obj_name + "#" + str(j)
point.only_for_player = True
ingress_point.targets.append(location)
flight.points.append(point)
else:
point = FlightWaypoint(location.position.x, location.position.y, 0)
point.description = "STRIKE on " + location.obj_name + " " + str(location.category)
point.pretty_name = "STRIKE on " + location.obj_name + " " + str(location.category)
point.targets.append(location)
flight.points.append(point)
if hasattr(location, "obj_name"):
buildings = self.game.theater.find_ground_objects_by_obj_name(location.obj_name)
print(buildings)
for building in buildings:
print("BUILDING " + str(building.is_dead) + " " + str(building.dcs_identifier))
if building.is_dead:
continue
point = FlightWaypoint(building.position.x, building.position.y, 0)
point.description = "STRIKE on " + building.obj_name + " " + str(building.category)
point.pretty_name = "STRIKE on " + building.obj_name + " " + str(building.category)
point.name = building.obj_name
point.only_for_player = True
ingress_point.targets.append(building)
flight.points.append(point)
else:
point = FlightWaypoint(location.position.x, location.position.y, 0)
point.description = "STRIKE on " + location.obj_name + " " + str(location.category)
point.pretty_name = "STRIKE on " + location.obj_name + " " + str(location.category)
point.name = location.obj_name
point.only_for_player = True
ingress_point.targets.append(location)
flight.points.append(point)
egress_pos = location.position.point_from_heading(egress_heading, INGRESS_EGRESS_DISTANCE)
egress_point = FlightWaypoint(egress_pos.x, egress_pos.y, EGRESS_ALT)
egress_point.pretty_name = "EGRESS on " + location.obj_name
egress_point.description = "EGRESS on " + location.obj_name
egress_point.name = "EGRESS"
egress_point.pretty_name = "EGRESS from " + location.obj_name
egress_point.description = "EGRESS from " + location.obj_name
egress_point.waypoint_type = FlightWaypointType.EGRESS
flight.points.append(egress_point)
descend = self.generate_descend_point(self.from_cp)
flight.points.append(descend)
rtb = self.generate_rtb_waypoint(self.from_cp)
flight.points.append(rtb)
self.strike_flights.append(flight)
self.flights.append(flight)
@@ -445,3 +563,35 @@ class FlightPlanner:
del base_aircraft_inventory[f.unit_type]
return base_aircraft_inventory
def generate_ascend_point(self, from_cp):
ascend_heading = from_cp.heading
pos_ascend = from_cp.position.point_from_heading(ascend_heading, 10000)
ascend = FlightWaypoint(pos_ascend.x, pos_ascend.y, PATTERN_ALTITUDE)
ascend.name = "ASCEND"
ascend.alt_type = "RADIO"
ascend.description = "Ascend to alt [" + str(meter_to_feet(PATTERN_ALTITUDE)) + " ft AGL], then proceed to next waypoint"
ascend.pretty_name = "Ascend to alt [" + str(meter_to_feet(PATTERN_ALTITUDE)) + " ft AGL]"
ascend.waypoint_type = FlightWaypointType.ASCEND_POINT
return ascend
def generate_descend_point(self, from_cp):
ascend_heading = from_cp.heading
descend = from_cp.position.point_from_heading(ascend_heading - 180, 30000)
descend = FlightWaypoint(descend.x, descend.y, PATTERN_ALTITUDE)
descend.name = "DESCEND"
descend.alt_type = "RADIO"
descend.description = "Descend to pattern alt [" + str(meter_to_feet(PATTERN_ALTITUDE)) + " ft AGL], contact tower, and land"
descend.pretty_name = "Descend to pattern alt [" + str(meter_to_feet(PATTERN_ALTITUDE)) + " ft AGL]"
descend.waypoint_type = FlightWaypointType.DESCENT_POINT
return descend
def generate_rtb_waypoint(self, from_cp):
rtb = from_cp.position
rtb = FlightWaypoint(rtb.x, rtb.y, 0)
rtb.name = "LANDING"
rtb.alt_type = "RADIO"
rtb.description = "RTB"
rtb.pretty_name = "RTB"
rtb.waypoint_type = FlightWaypointType.LANDING_POINT
return rtb

View File

@@ -48,6 +48,7 @@ CAP_CAPABLE = [
P_51D_30_NA,
P_51D,
P_47D_30,
SpitfireLFMkIXCW,
SpitfireLFMkIX,
@@ -106,6 +107,7 @@ CAS_CAPABLE = [
P_51D_30_NA,
P_51D,
P_47D_30,
A_20G,
SpitfireLFMkIXCW,
@@ -165,10 +167,9 @@ STRIKE_CAPABLE = [
L_39ZA,
AJS37,
M_2000C,
P_51D_30_NA,
P_51D,
P_47D_30,
A_20G,
SpitfireLFMkIXCW,

View File

@@ -30,17 +30,38 @@ class FlightType(Enum):
EWAR = 16
class FlightWaypoint():
class FlightWaypointType(Enum):
TAKEOFF = 0 # Take off point
ASCEND_POINT = 1 # Ascension point after take off
PATROL = 2 # Patrol point
PATROL_TRACK = 3 # Patrol race track
NAV = 4 # Nav point
INGRESS_STRIKE = 5 # Ingress strike (For generator, means that this should have bombing on next TARGET_POINT points)
INGRESS_SEAD = 6 # Ingress sead (For generator, means that this should attack groups on TARGET_GROUP_LOC points)
INGRESS_CAS = 7 # Ingress cas (should start CAS task)
CAS = 8 # Should do CAS there
EGRESS = 9 # Should stop attack
DESCENT_POINT = 10 # Should start descending to pattern alt
LANDING_POINT = 11 # Should land there
TARGET_POINT = 12 # A target building or static object, position
TARGET_GROUP_LOC = 13 # A target group approximate location
TARGET_SHIP = 14 # A target ship known location
class FlightWaypoint:
def __init__(self, x: float, y: float, alt=0):
self.x = x
self.y = y
self.alt = alt
self.alt_type = "BARO"
self.name = ""
self.description = ""
self.targets = []
self.obj_name = ""
self.pretty_name = ""
self.waypoint_type = FlightWaypointType.TAKEOFF # type: FlightWaypointType
self.only_for_player = False
class Flight:

View File

@@ -4,6 +4,7 @@ import sys
from shutil import copyfile
import dcs
from PySide2 import QtWidgets
from PySide2.QtGui import QPixmap
from PySide2.QtWidgets import QApplication, QSplashScreen
from dcs import installation
@@ -11,12 +12,22 @@ from dcs import installation
from qt_ui import uiconstants
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
from qt_ui.windows.QLiberationWindow import QLiberationWindow
from userdata import persistency, logging as logging_module
from qt_ui.windows.preferences.QLiberationFirstStartWindow import QLiberationFirstStartWindow
from userdata import persistency, logging as logging_module, liberation_install
if __name__ == "__main__":
persistency.setup(installation.get_dcs_saved_games_directory())
app = QApplication(sys.argv)
css = ""
with open("./resources/stylesheets/style.css") as stylesheet:
app.setStyleSheet(stylesheet.read())
# Logging setup
VERSION_STRING = "2.0RC6"
logging_module.setup_version_string(VERSION_STRING)
# Inject custom payload in pydcs framework
custom_payloads = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..\\resources\\customized_payloads")
if os.path.exists(custom_payloads):
dcs.planes.FlyingType.payload_dirs.append(custom_payloads)
@@ -27,11 +38,14 @@ if __name__ == "__main__":
if os.path.exists(custom_payloads):
dcs.planes.FlyingType.payload_dirs.append(custom_payloads)
VERSION_STRING = "2.0RC6"
logging_module.setup_version_string(VERSION_STRING)
logging.info("Using {} as userdata folder".format(persistency.base_path()))
app = QApplication(sys.argv)
first_start = liberation_install.init()
if first_start:
window = QLiberationFirstStartWindow()
window.exec_()
logging.info("Using {} as 'Saved Game Folder'".format(persistency.base_path()))
logging.info("Using {} as 'DCS installation folder'".format(liberation_install.get_dcs_install_directory()))
# Splash screen setup
pixmap = QPixmap("./resources/ui/splash_screen.png")
@@ -44,24 +58,28 @@ if __name__ == "__main__":
uiconstants.load_aircraft_icons()
uiconstants.load_vehicle_icons()
css = ""
with open("./resources/stylesheets/style.css") as stylesheet:
css = stylesheet.read()
# Replace DCS Mission scripting file to allow DCS Liberation to work
print("Replace : " + installation.get_dcs_install_directory() + os.path.sep + "Scripts/MissionScripting.lua")
copyfile("./resources/scripts/MissionScripting.lua", installation.get_dcs_install_directory() + os.path.sep + "Scripts/MissionScripting.lua")
app.processEvents()
try:
liberation_install.replace_mission_scripting_file()
except:
error_dialog = QtWidgets.QErrorMessage()
error_dialog.setWindowTitle("Wrong DCS installation directory.")
error_dialog.showMessage("Unable to modify Mission Scripting file. Possible issues with rights. Try running as admin, or please perform the modification of the MissionScripting file manually.")
error_dialog.exec_()
# Apply CSS (need works)
app.setStyleSheet(css)
GameUpdateSignal()
# Start window
window = QLiberationWindow()
window.showMaximized()
splash.finish(window)
sys.exit(app.exec_())
qt_execution_code = app.exec_()
# Restore Mission Scripting file
logging.info("QT App terminated with status code : " + str(qt_execution_code))
logging.info("Attempt to restore original mission scripting file")
liberation_install.restore_original_mission_scripting()
sys.exit(qt_execution_code)

View File

@@ -8,12 +8,12 @@ from game.event import UnitsDeliveryEvent, FrontlineAttackEvent
from theater.theatergroundobject import CATEGORY_MAP
URLS : Dict[str, str] = {
"Manual": "https://github.com/shdwp/dcs_liberation/wiki/Manual",
"Manual": "https://github.com/khopa/dcs_liberation/wiki",
"Troubleshooting": "https://github.com/shdwp/dcs_liberation/wiki/Troubleshooting",
"Modding": "https://github.com/shdwp/dcs_liberation/wiki/Modding-tutorial",
"Repository": "https://github.com/shdwp/dcs_liberation",
"Repository": "https://github.com/khopa/dcs_liberation",
"ForumThread": "https://forums.eagle.ru/showthread.php?t=214834",
"Issues": "https://github.com/shdwp/dcs_liberation/issues"
"Issues": "https://github.com/khopa/dcs_liberation/issues"
}
LABELS_OPTIONS = ["Full", "Abbreviated", "Dot Only", "Off"]

View File

@@ -93,15 +93,16 @@ class QPredefinedWaypointSelectionComboBox(QComboBox):
pos = Conflict.frontline_position(self.game.theater, cp, ecp)[0]
wpt = FlightWaypoint(pos.x, pos.y, 800)
wpt.name = "Frontline " + cp.name + "/" + ecp.name + " [CAS]"
wpt.alt_type = "RADIO"
wpt.pretty_name = wpt.name
wpt.description = "Frontline"
i = add_model_item(i, model, wpt.pretty_name, wpt)
for cp in self.game.theater.controlpoints:
for ground_object in cp.ground_objects:
if not ground_object.is_dead and not ground_object.dcs_identifier == "AA":
wpt = FlightWaypoint(ground_object.position.x,ground_object.position.y, 0)
wpt.alt_type = "RADIO"
wpt.name = wpt.name = "[" + str(ground_object.obj_name) + "] : " + ground_object.category + " #" + str(ground_object.object_id)
wpt.pretty_name = wpt.name
wpt.obj_name = ground_object.obj_name
@@ -119,6 +120,7 @@ class QPredefinedWaypointSelectionComboBox(QComboBox):
for g in ground_object.groups:
for j, u in enumerate(g.units):
wpt = FlightWaypoint(u.position.x, u.position.y, 0)
wpt.alt_type = "RADIO"
wpt.name = wpt.name = "[" + str(ground_object.obj_name) + "] : " + u.type + " #" + str(j)
wpt.pretty_name = wpt.name
wpt.targets.append(u)
@@ -132,6 +134,7 @@ class QPredefinedWaypointSelectionComboBox(QComboBox):
for cp in self.game.theater.controlpoints:
wpt = FlightWaypoint(cp.position.x, cp.position.y, 0)
wpt.alt_type = "RADIO"
wpt.name = cp.name
if cp.captured:
wpt.description = "Position of " + cp.name + " [Friendly Airbase]"

View File

@@ -83,11 +83,16 @@ class QLiberationMap(QGraphicsView):
pen = QPen(brush=CONST.COLORS["red"])
brush = CONST.COLORS["red_transparent"]
added_objects = []
for ground_object in cp.ground_objects:
if ground_object.obj_name in added_objects:
continue
go_pos = self._transform_point(ground_object.position)
if not ground_object.airbase_group:
scene.addItem(QMapGroundObject(self, go_pos[0], go_pos[1], 12, 12, cp, ground_object))
buildings = self.game.theater.find_ground_objects_by_obj_name(ground_object.obj_name)
scene.addItem(QMapGroundObject(self, go_pos[0], go_pos[1], 12, 12, cp, ground_object, buildings))
if ground_object.category == "aa" and self.get_display_rule("sam"):
max_range = 0
@@ -99,6 +104,7 @@ class QLiberationMap(QGraphicsView):
max_range = unit.threat_range
if max_range >= 6000:
scene.addEllipse(go_pos[0] - max_range/300.0 + 8, go_pos[1] - max_range/300.0 + 8, max_range/150.0, max_range/150.0, pen, brush)
added_objects.append(ground_object.obj_name)
for cp in self.game.theater.enemy_points():
if self.get_display_rule("lines"):

View File

@@ -9,13 +9,14 @@ from theater import TheaterGroundObject, ControlPoint
class QMapGroundObject(QGraphicsRectItem):
def __init__(self, parent, x: float, y: float, w: float, h: float, cp: ControlPoint, model: TheaterGroundObject):
def __init__(self, parent, x: float, y: float, w: float, h: float, cp: ControlPoint, model: TheaterGroundObject, buildings=[]):
super(QMapGroundObject, self).__init__(x, y, w, h)
self.model = model
self.cp = cp
self.parent = parent
self.setAcceptHoverEvents(True)
self.setZValue(2)
self.buildings = buildings
#self.setFlag(QGraphicsItem.ItemIgnoresTransformations, True)
if len(self.model.groups) > 0:
@@ -32,7 +33,11 @@ class QMapGroundObject(QGraphicsRectItem):
tooltip = tooltip + str(unit) + "x" + str(units[unit]) + "\n"
self.setToolTip(tooltip[:-1])
else:
self.setToolTip("[" + self.model.obj_name + "] : " + self.model.category)
tooltip = "[" + self.model.obj_name + "]" + "\n"
for building in buildings:
if not building.is_dead:
tooltip = tooltip + str(building.dcs_identifier) + "\n"
self.setToolTip(tooltip[:-1])
def paint(self, painter, option, widget=None):

View File

@@ -57,6 +57,14 @@ class QDebriefingWindow(QDialog):
except:
print("Issue adding " + str(unit_type) + " to debriefing information")
for building, count in self.debriefing.player_dead_buildings_dict.items():
try:
lostUnitsLayout.addWidget(QLabel(building, row, 0))
lostUnitsLayout.addWidget(QLabel("{}".format(count)), row, 1)
row += 1
except:
print("Issue adding " + str(building) + " to debriefing information")
self.layout.addWidget(lostUnits)
# Enemy lost units
@@ -87,6 +95,14 @@ class QDebriefingWindow(QDialog):
enemylostUnitsLayout.addWidget(QLabel("{}".format(count)), row, 1)
row += 1
for building, count in self.debriefing.enemy_dead_buildings_dict.items():
try:
enemylostUnitsLayout.addWidget(QLabel(building), row, 0)
enemylostUnitsLayout.addWidget(QLabel("{}".format(count)), row, 1)
row += 1
except:
print("Issue adding " + str(building) + " to debriefing information")
self.layout.addWidget(enemylostUnits)
# confirm button

View File

@@ -1,10 +1,9 @@
import sys
import webbrowser
from PySide2 import QtGui
from PySide2.QtCore import Qt
from PySide2.QtGui import QIcon
from PySide2.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QMainWindow, QAction, QMessageBox, QDesktopWidget, \
from PySide2.QtWidgets import QWidget, QVBoxLayout, QMainWindow, QAction, QMessageBox, QDesktopWidget, \
QSplitter
import qt_ui.uiconstants as CONST
@@ -12,10 +11,12 @@ from game import Game
from qt_ui.uiconstants import URLS
from qt_ui.widgets.QTopPanel import QTopPanel
from qt_ui.widgets.map.QLiberationMap import QLiberationMap
from qt_ui.windows.preferences import QLiberationPreferences
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal, DebriefingSignal
from qt_ui.windows.QDebriefingWindow import QDebriefingWindow
from qt_ui.windows.QNewGameWizard import NewGameWizard
from qt_ui.windows.infos.QInfoPanel import QInfoPanel
from qt_ui.windows.preferences.QLiberationPreferencesWindow import QLiberationPreferencesWindow
from userdata import persistency
@@ -79,6 +80,10 @@ class QLiberationWindow(QMainWindow):
self.showAboutDialogAction.setIcon(QIcon.fromTheme("help-about"))
self.showAboutDialogAction.triggered.connect(self.showAboutDialog)
self.showLiberationPrefDialogAction = QAction("Preferences", self)
self.showLiberationPrefDialogAction.setIcon(QIcon.fromTheme("help-about"))
self.showLiberationPrefDialogAction.triggered.connect(self.showLiberationDialog)
def initToolbar(self):
self.tool_bar = self.addToolBar("File")
self.tool_bar.addAction(self.newGameAction)
@@ -92,17 +97,21 @@ class QLiberationWindow(QMainWindow):
file_menu.addAction(self.newGameAction)
#file_menu.addAction(QIcon(CONST.ICONS["Open"]), "Open") # TODO : implement
file_menu.addAction(self.saveGameAction)
file_menu.addSeparator()
file_menu.addAction(self.showLiberationPrefDialogAction)
file_menu.addSeparator()
#file_menu.addAction("Save As") # TODO : implement
#file_menu.addAction("Close Current Game", lambda: self.closeGame()) # Not working
file_menu.addAction("Exit" , lambda: self.exit())
help_menu = self.menu.addMenu("Help")
#help_menu.addAction("Online Manual", lambda: webbrowser.open_new_tab(URLS["Manual"]))
help_menu.addAction("Online Manual", lambda: webbrowser.open_new_tab(URLS["Manual"]))
help_menu.addAction("Discord", lambda: webbrowser.open_new_tab("https://" + "discord.gg" + "/" + "bKrt" + "rkJ"))
#help_menu.addAction("Troubleshooting Guide", lambda: webbrowser.open_new_tab(URLS["Troubleshooting"]))
#help_menu.addAction("Modding Guide", lambda: webbrowser.open_new_tab(URLS["Modding"]))
#help_menu.addSeparator() ----> Note from Khopa : I disable these links since it's not up to date for this branch
help_menu.addAction("Contribute", lambda: webbrowser.open_new_tab(URLS["Repository"]))
#help_menu.addAction("Contribute", lambda: webbrowser.open_new_tab(URLS["Repository"]))
help_menu.addAction("Forum Thread", lambda: webbrowser.open_new_tab(URLS["ForumThread"]))
help_menu.addAction("Report an issue", lambda: webbrowser.open_new_tab(URLS["Issues"]))
help_menu.addSeparator()
@@ -192,6 +201,10 @@ class QLiberationWindow(QMainWindow):
print(about.textFormat())
about.exec_()
def showLiberationDialog(self):
self.subwindow = QLiberationPreferencesWindow()
self.subwindow.show()
def onDebriefing(self, debrief: DebriefingSignal):
print("On Debriefing")
self.debriefing = QDebriefingWindow(debrief.debriefing, debrief.gameEvent, debrief.game)

View File

@@ -0,0 +1,80 @@
from PySide2.QtGui import QIcon, Qt
from PySide2.QtWidgets import QDialog, QVBoxLayout, QPushButton, QHBoxLayout, QPlainTextEdit, QTextEdit
from qt_ui.windows.preferences.QLiberationPreferences import QLiberationPreferences
class QLiberationFirstStartWindow(QDialog):
def __init__(self):
super(QLiberationFirstStartWindow, self).__init__()
self.setModal(True)
self.setWindowTitle("First start configuration")
self.setMinimumSize(500, 200)
self.setWindowIcon(QIcon("./resources/icon.png"))
self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.Dialog | Qt.WindowTitleHint)
self.setWindowModality(Qt.WindowModal)
self.preferences = QLiberationPreferences()
WARN_TEXT = """
<strong>Welcome to DCS Liberation !</strong>
<br/><br>
<strong>Please take 30 seconds to read this :</strong>
<p>DCS Liberation will modify this file in your DCS installation directory :</p>
<br/>
<strong>&lt;dcs_installation_directory&gt;/Scripts/MissionScripting.lua</strong><br/>
<p>
This will disable some security limits of the DCS World Lua scripting environment, in order to allow communication between DCS World and DCS Liberation.
However, the modification of this file could potentially grant access to your filesystem to malicious DCS mission files.
</p>
<p>So, you should not join untrusted servers or open untrusted mission files within DCS world while DCS Liberation is running.</p>
<p>
DCS Liberation will restore your original MissionScripting file when it close.
</p>
<p>
However, should DCS Liberation encounter an unexpected crash (which should not happen), the MissionScripting file might not be restored.
If that occurs, you can use the backup file saved in the DCS Liberation directory there :
</p>
<br/>
<strong>./resources/scripts/MissionScripting.original.lua</strong><br/>
<p>Then copy it in your DCS installation directory to replace this file :</p>
<br/>
<strong>&lt;dcs_installation_directory&gt;/Scripts/MissionScripting.lua</strong><br/>
<p>As you click on the button below, the file will be replaced in your DCS installation directory.</p>
<br/><br/>
<strong>Thank you for reading !</strong>
"""
self.warning_text = QTextEdit(WARN_TEXT)
self.warning_text.setReadOnly(True)
self.apply_button = QPushButton("I have read everything and I Accept")
self.apply_button.clicked.connect(lambda : self.apply())
self.initUI()
def initUI(self):
layout = QVBoxLayout()
layout.addWidget(self.preferences)
layout.addWidget(self.warning_text)
layout.addStretch()
apply_btn_layout = QHBoxLayout()
apply_btn_layout.addStretch()
apply_btn_layout.addWidget(self.apply_button)
layout.addLayout(apply_btn_layout)
self.setLayout(layout)
def apply(self):
print("Applying changes")
if self.preferences.apply():
self.close()

View File

@@ -0,0 +1,93 @@
import os
from PySide2 import QtWidgets
from PySide2.QtGui import Qt
from PySide2.QtWidgets import QFrame, QLineEdit, QGridLayout, QVBoxLayout, QLabel, QPushButton, \
QFileDialog, QMessageBox, QDialog
from userdata import liberation_install
class QLiberationPreferences(QFrame):
def __init__(self):
super(QLiberationPreferences, self).__init__()
self.saved_game_dir = ""
self.dcs_install_dir = ""
self.dcs_install_dir = liberation_install.get_dcs_install_directory()
self.saved_game_dir = liberation_install.get_saved_game_dir()
self.edit_dcs_install_dir = QLineEdit(self.dcs_install_dir)
self.edit_saved_game_dir = QLineEdit(self.saved_game_dir)
self.edit_dcs_install_dir.setMinimumWidth(300)
self.edit_saved_game_dir.setMinimumWidth(300)
self.browse_saved_game = QPushButton("Browse...")
self.browse_saved_game.clicked.connect(self.on_browse_saved_games)
self.browse_install_dir = QPushButton("Browse...")
self.browse_install_dir.clicked.connect(self.on_browse_installation_dir)
self.initUi()
def initUi(self):
main_layout = QVBoxLayout()
layout = QGridLayout()
layout.addWidget(QLabel("<strong>DCS saved game directory:</strong>"), 0, 0, alignment=Qt.AlignLeft)
layout.addWidget(self.edit_saved_game_dir, 1, 0, alignment=Qt.AlignRight)
layout.addWidget(self.browse_saved_game, 1, 1, alignment=Qt.AlignRight)
layout.addWidget(QLabel("<strong>DCS installation directory:</strong>"), 2, 0, alignment=Qt.AlignLeft)
layout.addWidget(self.edit_dcs_install_dir, 3, 0, alignment=Qt.AlignRight)
layout.addWidget(self.browse_install_dir, 3, 1, alignment=Qt.AlignRight)
main_layout.addLayout(layout)
main_layout.addStretch()
self.setLayout(main_layout)
def on_browse_saved_games(self):
saved_game_dir = str(QFileDialog.getExistingDirectory(self, "Select DCS Saved Game Directory"))
if saved_game_dir:
self.saved_game_dir = saved_game_dir
self.edit_saved_game_dir.setText(saved_game_dir)
def on_browse_installation_dir(self):
install_dir = str(QFileDialog.getExistingDirectory(self, "Select DCS Installation Directory"))
if install_dir:
self.dcs_install_dir = install_dir
self.edit_dcs_install_dir.setText(install_dir)
def apply(self):
print("Applying changes")
self.saved_game_dir = self.edit_saved_game_dir.text()
self.dcs_install_dir = self.edit_dcs_install_dir.text()
if not os.path.isdir(self.saved_game_dir):
error_dialog = QMessageBox.critical(self, "Wrong DCS Saved Games directory.",
self.saved_game_dir + " is not a valid directory",
QMessageBox.StandardButton.Ok)
error_dialog.exec_()
return False
if not os.path.isdir(self.dcs_install_dir):
error_dialog = QMessageBox.critical(self, "Wrong DCS installation directory.",
self.dcs_install_dir + " is not a valid directory",
QMessageBox.StandardButton.Ok)
error_dialog.exec_()
return False
if not os.path.isdir(os.path.join(self.dcs_install_dir, "Scripts")) and os.path.isfile(os.path.join(self.dcs_install_dir, "bin", "DCS.exe")):
error_dialog = QMessageBox.critical(self, "Wrong DCS installation directory.",
self.dcs_install_dir + " is not a valid DCS installation directory",
QMessageBox.StandardButton.Ok)
error_dialog.exec_()
return False
liberation_install.setup(self.saved_game_dir, self.dcs_install_dir)
liberation_install.save_config()
return True

View File

@@ -0,0 +1,37 @@
from PySide2.QtGui import QIcon, Qt
from PySide2.QtWidgets import QDialog, QVBoxLayout, QPushButton, QHBoxLayout
from qt_ui.windows.preferences.QLiberationPreferences import QLiberationPreferences
class QLiberationPreferencesWindow(QDialog):
def __init__(self):
super(QLiberationPreferencesWindow, self).__init__()
self.setModal(True)
self.setWindowTitle("Preferences")
self.setMinimumSize(300, 200)
self.setWindowIcon(QIcon("./resources/icon.png"))
self.preferences = QLiberationPreferences()
self.apply_button = QPushButton("Apply")
self.apply_button.clicked.connect(lambda : self.apply())
self.initUI()
def initUI(self):
layout = QVBoxLayout()
layout.addWidget(self.preferences)
layout.addStretch()
apply_btn_layout = QHBoxLayout()
apply_btn_layout.addStretch()
apply_btn_layout.addWidget(self.apply_button)
layout.addLayout(apply_btn_layout)
self.setLayout(layout)
def apply(self):
if self.preferences.apply():
print("Closing")
self.close()
else:
print("Not Closing")

View File

@@ -98,11 +98,11 @@ class QSettingsWindow(QDialog):
self.enemyAASkill.currentIndexChanged.connect(self.applySettings)
self.difficultyLayout.addWidget(QLabel("Player coalition skill"), 0, 0)
self.difficultyLayout.addWidget(self.playerCoalitionSkill, 0, 1)
self.difficultyLayout.addWidget(self.playerCoalitionSkill, 0, 1, Qt.AlignRight)
self.difficultyLayout.addWidget(QLabel("Enemy skill"), 1, 0)
self.difficultyLayout.addWidget(self.enemyCoalitionSkill, 1, 1)
self.difficultyLayout.addWidget(self.enemyCoalitionSkill, 1, 1, Qt.AlignRight)
self.difficultyLayout.addWidget(QLabel("Enemy AA and vehicles skill"), 2, 0)
self.difficultyLayout.addWidget(self.enemyAASkill, 2, 1)
self.difficultyLayout.addWidget(self.enemyAASkill, 2, 1, Qt.AlignRight)
self.difficultyLabel = QComboBox()
[self.difficultyLabel.addItem(t) for t in CONST.LABELS_OPTIONS]
@@ -110,13 +110,13 @@ class QSettingsWindow(QDialog):
self.difficultyLabel.currentIndexChanged.connect(self.applySettings)
self.difficultyLayout.addWidget(QLabel("In Game Labels"), 3, 0)
self.difficultyLayout.addWidget(self.difficultyLabel, 3, 1)
self.difficultyLayout.addWidget(self.difficultyLabel, 3, 1, Qt.AlignRight)
self.noNightMission = QCheckBox()
self.noNightMission.setChecked(self.game.settings.night_disabled)
self.noNightMission.toggled.connect(self.applySettings)
self.difficultyLayout.addWidget(QLabel("No night missions"), 4, 0)
self.difficultyLayout.addWidget(self.noNightMission, 4, 1)
self.difficultyLayout.addWidget(self.noNightMission, 4, 1, Qt.AlignRight)
def initGeneratorLayout(self):
@@ -135,7 +135,7 @@ class QSettingsWindow(QDialog):
self.supercarrier.toggled.connect(self.applySettings)
self.gameplayLayout.addWidget(QLabel("Use Supercarrier Module"), 0, 0)
self.gameplayLayout.addWidget(self.supercarrier, 0, 1)
self.gameplayLayout.addWidget(self.supercarrier, 0, 1, Qt.AlignRight)
self.performance = QGroupBox("Performance")
self.performanceLayout = QGridLayout();
@@ -162,16 +162,22 @@ class QSettingsWindow(QDialog):
self.infantry.setChecked(self.game.settings.perf_infantry)
self.infantry.toggled.connect(self.applySettings)
self.ai_parking_start = QCheckBox()
self.ai_parking_start.setChecked(self.game.settings.perf_ai_parking_start)
self.ai_parking_start.toggled.connect(self.applySettings)
self.performanceLayout.addWidget(QLabel("Smoke visual effect on frontline"), 0, 0)
self.performanceLayout.addWidget(self.smoke, 0, 1)
self.performanceLayout.addWidget(self.smoke, 0, 1, alignment=Qt.AlignRight)
self.performanceLayout.addWidget(QLabel("SAM starts in RED alert mode"), 1, 0)
self.performanceLayout.addWidget(self.red_alert, 1, 1)
self.performanceLayout.addWidget(self.red_alert, 1, 1, alignment=Qt.AlignRight)
self.performanceLayout.addWidget(QLabel("Artillery strikes"), 2, 0)
self.performanceLayout.addWidget(self.arti, 2, 1)
self.performanceLayout.addWidget(self.arti, 2, 1, alignment=Qt.AlignRight)
self.performanceLayout.addWidget(QLabel("Moving ground units"), 3, 0)
self.performanceLayout.addWidget(self.moving_units, 3, 1)
self.performanceLayout.addWidget(self.moving_units, 3, 1, alignment=Qt.AlignRight)
self.performanceLayout.addWidget(QLabel("Generate infantry squads along vehicles"), 4, 0)
self.performanceLayout.addWidget(self.infantry, 4, 1)
self.performanceLayout.addWidget(self.infantry, 4, 1, alignment=Qt.AlignRight)
self.performanceLayout.addWidget(QLabel("AI planes parking start (AI starts in flight if disabled)"), 5, 0)
self.performanceLayout.addWidget(self.ai_parking_start, 5, 1, alignment=Qt.AlignRight)
self.generatorLayout.addWidget(self.gameplay)
self.generatorLayout.addWidget(QLabel("Disabling settings below may improve performance, but will impact the overall quality of the experience."))
@@ -230,6 +236,7 @@ class QSettingsWindow(QDialog):
self.game.settings.perf_artillery = self.arti.isChecked()
self.game.settings.perf_moving_units = self.moving_units.isChecked()
self.game.settings.perf_infantry = self.infantry.isChecked()
self.game.settings.perf_ai_parking_start = self.ai_parking_start.isChecked()
GameUpdateSignal.get_instance().updateGame(self.game)

View File

@@ -1,3 +1,4 @@
pydcs>=0.9.4
Pyside2>=5.13.0
pyinstaller==3.5
pyproj==2.6.1.post1

View File

@@ -73,33 +73,29 @@ local unitPayloads = {
["name"] = "STRIKE",
["pylons"] = {
[1] = {
["CLSID"] = "DIS_WMD7",
["num"] = 4,
["CLSID"] = "DIS_LS_6_500",
["num"] = 6,
},
[2] = {
["CLSID"] = "DIS_LS_6_500",
["num"] = 5,
},
[3] = {
["CLSID"] = "DIS_LS_6_500",
["num"] = 3,
},
[4] = {
["CLSID"] = "DIS_LS_6_500",
["num"] = 2,
},
[5] = {
["CLSID"] = "DIS_PL-5EII",
["num"] = 1,
},
[3] = {
[6] = {
["CLSID"] = "DIS_PL-5EII",
["num"] = 7,
},
[4] = {
["CLSID"] = "DIS_GBU_12_DUAL",
["num"] = 6,
},
[5] = {
["CLSID"] = "DIS_GBU_12_DUAL",
["num"] = 2,
},
[6] = {
["CLSID"] = "DIS_GBU_16",
["num"] = 3,
},
[7] = {
["CLSID"] = "DIS_GBU_16",
["num"] = 5,
},
},
["tasks"] = {
[1] = 10,

View File

@@ -0,0 +1,85 @@
local unitPayloads = {
["name"] = "P-47D-30",
["payloads"] = {
[1] = {
["name"] = "CAS",
["pylons"] = {
[1] = {
["CLSID"] = "{AN-M64}",
["num"] = 3,
},
[2] = {
["CLSID"] = "{AN-M64}",
["num"] = 2,
},
[3] = {
["CLSID"] = "{AN-M64}",
["num"] = 1,
},
},
["tasks"] = {
[1] = 11,
},
},
[2] = {
["name"] = "STRIKE",
["pylons"] = {
[1] = {
["CLSID"] = "{AN_M65}",
["num"] = 3,
},
[2] = {
["CLSID"] = "{AN_M65}",
["num"] = 2,
},
[3] = {
["CLSID"] = "{AN-M64}",
["num"] = 1,
},
},
["tasks"] = {
[1] = 11,
},
},
[3] = {
["name"] = "ANTISTRIKE",
["pylons"] = {
},
["tasks"] = {
[1] = 11,
},
},
[4] = {
["name"] = "CAP",
["pylons"] = {
},
["tasks"] = {
[1] = 11,
},
},
[5] = {
["name"] = "SEAD",
["pylons"] = {
[1] = {
["CLSID"] = "{AN_M57}",
["num"] = 3,
},
[2] = {
["CLSID"] = "{AN_M57}",
["num"] = 2,
},
[3] = {
["CLSID"] = "{AN_M57}",
["num"] = 1,
},
},
["tasks"] = {
[1] = 11,
},
},
},
["tasks"] = {
},
["unitType"] = "P-47D-30",
}
return unitPayloads

View File

@@ -2,156 +2,150 @@ local unitPayloads = {
["name"] = "Su-24M",
["payloads"] = {
[1] = {
["name"] = "ANTISHIP",
["pylons"] = {
[1] = {
["CLSID"] = "{6DADF342-D4BA-4D8A-B081-BA928C4AF86D}",
["num"] = 1,
},
[2] = {
["CLSID"] = "{D8F2C90B-887B-4B9E-9FE2-996BC9E9AF03}",
["num"] = 2,
},
[3] = {
["CLSID"] = "{0519A264-0AB6-11d6-9193-00A0249B6F00}",
["num"] = 5,
},
[4] = {
["CLSID"] = "{D8F2C90B-887B-4B9E-9FE2-996BC9E9AF03}",
["num"] = 7,
},
[5] = {
["CLSID"] = "{6DADF342-D4BA-4D8A-B081-BA928C4AF86D}",
["num"] = 8,
},
},
["tasks"] = {
[1] = 29,
[2] = 30,
},
},
[2] = {
["name"] = "CAS",
["pylons"] = {
[1] = {
["CLSID"] = "{292960BB-6518-41AC-BADA-210D65D5073C}",
["num"] = 1,
},
[2] = {
["CLSID"] = "{292960BB-6518-41AC-BADA-210D65D5073C}",
["num"] = 2,
},
[3] = {
["CLSID"] = "{292960BB-6518-41AC-BADA-210D65D5073C}",
["num"] = 7,
},
[4] = {
["CLSID"] = "{292960BB-6518-41AC-BADA-210D65D5073C}",
["num"] = 8,
},
[5] = {
["CLSID"] = "{3858707D-F5D5-4bbb-BDD8-ABB0530EBC7C}",
["num"] = 6,
},
[6] = {
["CLSID"] = "{3858707D-F5D5-4bbb-BDD8-ABB0530EBC7C}",
["num"] = 3,
},
[7] = {
["CLSID"] = "{3C612111-C7AD-476E-8A8E-2485812F4E5C}",
["num"] = 4,
},
[8] = {
["CLSID"] = "{3C612111-C7AD-476E-8A8E-2485812F4E5C}",
["num"] = 5,
},
},
["tasks"] = {
[1] = 31,
},
},
[3] = {
["name"] = "STRIKE",
["pylons"] = {
[1] = {
["CLSID"] = "{4203753F-8198-4E85-9924-6F8FF679F9FF}",
["num"] = 1,
},
[2] = {
["CLSID"] = "{4203753F-8198-4E85-9924-6F8FF679F9FF}",
["num"] = 2,
},
[3] = {
["CLSID"] = "{4203753F-8198-4E85-9924-6F8FF679F9FF}",
["num"] = 3,
},
[4] = {
["CLSID"] = "{4203753F-8198-4E85-9924-6F8FF679F9FF}",
["num"] = 4,
},
[5] = {
["CLSID"] = "{4203753F-8198-4E85-9924-6F8FF679F9FF}",
["num"] = 5,
},
[6] = {
["CLSID"] = "{4203753F-8198-4E85-9924-6F8FF679F9FF}",
["num"] = 6,
},
[7] = {
["CLSID"] = "{4203753F-8198-4E85-9924-6F8FF679F9FF}",
["num"] = 7,
},
[8] = {
["CLSID"] = "{4203753F-8198-4E85-9924-6F8FF679F9FF}",
["num"] = 8,
},
},
["tasks"] = {
[1] = 31,
},
},
[4] = {
["name"] = "SEAD",
["pylons"] = {
[1] = {
["CLSID"] = "{6DADF342-D4BA-4D8A-B081-BA928C4AF86D}",
["num"] = 1,
},
[2] = {
["CLSID"] = "{FE382A68-8620-4AC0-BDF5-709BFE3977D7}",
["num"] = 2,
},
[3] = {
["CLSID"] = "{0519A264-0AB6-11d6-9193-00A0249B6F00}",
["num"] = 5,
},
[4] = {
["CLSID"] = "{FE382A68-8620-4AC0-BDF5-709BFE3977D7}",
["num"] = 7,
},
[5] = {
["CLSID"] = "{6DADF342-D4BA-4D8A-B081-BA928C4AF86D}",
[2] = {
["CLSID"] = "{E86C5AA5-6D49-4F00-AD2E-79A62D6DDE26}",
["num"] = 8,
},
[3] = {
["CLSID"] = "{E86C5AA5-6D49-4F00-AD2E-79A62D6DDE26}",
["num"] = 1,
},
[4] = {
["CLSID"] = "{FE382A68-8620-4AC0-BDF5-709BFE3977D7}",
["num"] = 2,
},
},
["tasks"] = {
[1] = 29,
},
},
[5] = {
[2] = {
["name"] = "STRIKE",
["pylons"] = {
[1] = {
["CLSID"] = "{KAB_1500LG_LOADOUT}",
["num"] = 7,
},
[2] = {
["CLSID"] = "{3C612111-C7AD-476E-8A8E-2485812F4E5C}",
["num"] = 8,
},
[3] = {
["CLSID"] = "{3C612111-C7AD-476E-8A8E-2485812F4E5C}",
["num"] = 1,
},
[4] = {
["CLSID"] = "{KAB_1500LG_LOADOUT}",
["num"] = 2,
},
[5] = {
["CLSID"] = "{E2C426E3-8B10-4E09-B733-9CDC26520F48}",
["num"] = 3,
},
[6] = {
["CLSID"] = "{E2C426E3-8B10-4E09-B733-9CDC26520F48}",
["num"] = 6,
},
[7] = {
["CLSID"] = "{3C612111-C7AD-476E-8A8E-2485812F4E5C}",
["num"] = 4,
},
[8] = {
["CLSID"] = "{3C612111-C7AD-476E-8A8E-2485812F4E5C}",
["num"] = 5,
},
},
["tasks"] = {
},
},
[3] = {
["name"] = "CAP",
["pylons"] = {
[1] = {
["CLSID"] = "{B0DBC591-0F52-4F7D-AD7B-51E67725FB81}",
["num"] = 1,
["CLSID"] = "{7D7EC917-05F6-49D4-8045-61FC587DD019}",
["num"] = 7,
},
[2] = {
["CLSID"] = "{275A2855-4A79-4B2D-B082-91EA2ADF4691}",
["num"] = 8,
},
[3] = {
["CLSID"] = "{B0DBC591-0F52-4F7D-AD7B-51E67725FB81}",
["num"] = 1,
},
[4] = {
["CLSID"] = "{7D7EC917-05F6-49D4-8045-61FC587DD019}",
["num"] = 2,
},
},
["tasks"] = {
},
},
[4] = {
["name"] = "ANTISHIP",
["pylons"] = {
[1] = {
["CLSID"] = "{D8F2C90B-887B-4B9E-9FE2-996BC9E9AF03}",
["num"] = 7,
},
[2] = {
["CLSID"] = "{E86C5AA5-6D49-4F00-AD2E-79A62D6DDE26}",
["num"] = 8,
},
[3] = {
["CLSID"] = "{E86C5AA5-6D49-4F00-AD2E-79A62D6DDE26}",
["num"] = 1,
},
[4] = {
["CLSID"] = "{D8F2C90B-887B-4B9E-9FE2-996BC9E9AF03}",
["num"] = 2,
},
},
["tasks"] = {
},
},
[5] = {
["name"] = "CAS",
["pylons"] = {
[1] = {
["CLSID"] = "{6DADF342-D4BA-4D8A-B081-BA928C4AF86D}",
["num"] = 8,
},
[2] = {
["CLSID"] = "{6DADF342-D4BA-4D8A-B081-BA928C4AF86D}",
["num"] = 1,
},
[3] = {
["CLSID"] = "{3858707D-F5D5-4bbb-BDD8-ABB0530EBC7C}",
["num"] = 2,
},
[4] = {
["CLSID"] = "{3858707D-F5D5-4bbb-BDD8-ABB0530EBC7C}",
["num"] = 7,
},
[5] = {
["CLSID"] = "{3858707D-F5D5-4bbb-BDD8-ABB0530EBC7C}",
["num"] = 6,
},
[6] = {
["CLSID"] = "{3858707D-F5D5-4bbb-BDD8-ABB0530EBC7C}",
["num"] = 3,
},
[7] = {
["CLSID"] = "{3C612111-C7AD-476E-8A8E-2485812F4E5C}",
["num"] = 5,
},
[8] = {
["CLSID"] = "{3C612111-C7AD-476E-8A8E-2485812F4E5C}",
["num"] = 4,
},
},
["tasks"] = {
[1] = 32,
},
},
},

Binary file not shown.

View File

@@ -120,34 +120,13 @@ class Base:
target_dict[unit_type] = target_dict.get(unit_type, 0) + unit_count
def commit_losses(self, units_lost: typing.Dict[typing.Any, int]):
# advanced SAM sites have multiple units - this code was not at all set up to handle that
# to avoid having to restructure a bunch of upstream code, we track total destroyed units and
# use that to determine if a site was destroyed
# this can be thought of as the enemy re-distributing parts of SAM sites to keep as many
# operational as possible (pulling specific units from ...storage... to bring them back online
# if non-letal damage was done)
# in the future, I may add more depth to this (e.g. a base having a certain number of spares and tracking
# the number of pieces of each site), but for now this is what we get
sams_destroyed = {}
# we count complex SAM sites at the end - don't double count
aa_skip = [
AirDefence.SAM_SA_6_Kub_LN_2P25,
AirDefence.SAM_SA_3_S_125_LN_5P73,
AirDefence.SAM_SA_11_Buk_LN_9A310M1
]
for unit_type, count in units_lost.items():
if unit_type in db.SAM_CONVERT or unit_type in db.SAM_CONVERT['except']:
# unit is part of an advanced SAM site, which means it will fail the below check
try:
sams_destroyed[unit_type] += 1
except KeyError:
sams_destroyed[unit_type] = 1
if unit_type in self.aircraft:
target_array = self.aircraft
elif unit_type in self.armor:
target_array = self.armor
elif unit_type in self.aa and unit_type not in aa_skip:
target_array = self.aa
else:
print("Base didn't find event type {}".format(unit_type))
continue
@@ -160,22 +139,6 @@ class Base:
if target_array[unit_type] == 0:
del target_array[unit_type]
# now that we have a complete picture of the SAM sites destroyed, determine if any were destroyed
for sam_site, count in sams_destroyed.items():
dead_count = aaa.num_sam_dead(sam_site, count)
try:
modified_sam_site = db.SAM_CONVERT[sam_site]
except KeyError:
modified_sam_site = db.SAM_CONVERT[sam_site]['except']
if modified_sam_site in self.aa:
self.aa[modified_sam_site] = max(
self.aa[modified_sam_site] - dead_count,
0
)
if self.aa[modified_sam_site] == 0:
del self.aa[modified_sam_site]
def affect_strength(self, amount):
self.strength += amount
if self.strength > BASE_MAX_STRENGTH:

View File

@@ -180,7 +180,7 @@ class NorthCaucasus(ConflictTheater):
super(NorthCaucasus, self).__init__()
self.kutaisi = ControlPoint.from_airport(caucasus.Kutaisi, LAND, SIZE_SMALL, IMPORTANCE_LOW)
self.soganlug = ControlPoint.from_airport(caucasus.Soganlug, LAND, SIZE_SMALL, IMPORTANCE_LOW)
self.vaziani = ControlPoint.from_airport(caucasus.Vaziani, LAND, SIZE_SMALL, IMPORTANCE_LOW)
self.maykop = ControlPoint.from_airport(caucasus.Maykop_Khanskaya, LAND, SIZE_LARGE, IMPORTANCE_HIGH)
self.beslan = ControlPoint.from_airport(caucasus.Beslan, LAND, SIZE_REGULAR, IMPORTANCE_LOW)
self.nalchik = ControlPoint.from_airport(caucasus.Nalchik, LAND, SIZE_REGULAR, 1.1)
@@ -188,12 +188,12 @@ class NorthCaucasus(ConflictTheater):
self.mozdok = ControlPoint.from_airport(caucasus.Mozdok, LAND, SIZE_BIG, 1.1)
self.carrier_1 = ControlPoint.carrier("Carrier", mapping.Point(-285810.6875, 496399.1875))
self.soganlug.frontline_offset = 0.5
self.soganlug.base.strength = 1
self.vaziani.frontline_offset = 0.5
self.vaziani.base.strength = 1
self.add_controlpoint(self.kutaisi, connected_to=[self.soganlug])
self.add_controlpoint(self.soganlug, connected_to=[self.beslan, self.kutaisi])
self.add_controlpoint(self.beslan, connected_to=[self.soganlug, self.mozdok, self.nalchik])
self.add_controlpoint(self.kutaisi, connected_to=[self.vaziani])
self.add_controlpoint(self.vaziani, connected_to=[self.beslan, self.kutaisi])
self.add_controlpoint(self.beslan, connected_to=[self.vaziani, self.mozdok, self.nalchik])
self.add_controlpoint(self.nalchik, connected_to=[self.beslan, self.mozdok, self.mineralnye])
self.add_controlpoint(self.mozdok, connected_to=[self.nalchik, self.beslan, self.mineralnye])
self.add_controlpoint(self.mineralnye, connected_to=[self.nalchik, self.mozdok, self.maykop])
@@ -201,5 +201,5 @@ class NorthCaucasus(ConflictTheater):
self.add_controlpoint(self.carrier_1, connected_to=[])
self.carrier_1.captured = True
self.soganlug.captured = True
self.vaziani.captured = True
self.kutaisi.captured = True

View File

@@ -38,6 +38,7 @@ class ControlPoint:
cptype: ControlPointType = None
ICLS_counter = 1
alt = 0
def __init__(self, id: int, name: str, position: Point, at, radials: typing.Collection[int], size: int, importance: float,
has_frontline=True, cptype=ControlPointType.AIRBASE):
@@ -64,18 +65,21 @@ class ControlPoint:
self.tacanN = None
self.tacanI = "TAC"
self.icls = 0
self.airport = None
@classmethod
def from_airport(cls, airport: Airport, radials: typing.Collection[int], size: int, importance: float, has_frontline=True):
assert airport
return cls(airport.id, airport.name, airport.position, airport, radials, size, importance, has_frontline, cptype=ControlPointType.AIRBASE)
obj = cls(airport.id, airport.name, airport.position, airport, radials, size, importance, has_frontline, cptype=ControlPointType.AIRBASE)
obj.airport = airport()
return obj
@classmethod
def carrier(cls, name: str, at: Point, id: int = 1001):
import theater.conflicttheater
cp = cls(id, name, at, at, theater.conflicttheater.LAND, theater.conflicttheater.SIZE_SMALL, 1,
has_frontline=False, cptype=ControlPointType.AIRCRAFT_CARRIER_GROUP)
cp.tacanY = random.choice([True, False])
cp.tacanY = False
cp.tacanN = random.randint(26, 49)
cp.tacanI = random.choice(["STE", "CVN", "CVH", "CCV", "ACC", "ARC", "GER", "ABR", "LIN", "TRU"])
ControlPoint.ICLS_counter = ControlPoint.ICLS_counter + 1
@@ -87,11 +91,20 @@ class ControlPoint:
import theater.conflicttheater
cp = cls(id, name, at, at, theater.conflicttheater.LAND, theater.conflicttheater.SIZE_SMALL, 1,
has_frontline=False, cptype=ControlPointType.LHA_GROUP)
cp.tacanY = random.choice([True, False])
cp.tacanY = False
cp.tacanN = random.randint(1,25)
cp.tacanI = random.choice(["LHD", "LHA", "LHB", "LHC", "LHD", "LDS"])
return cp
@property
def heading(self):
if self.cptype == ControlPointType.AIRBASE:
return self.airport.runways[0].heading
elif self.cptype in [ControlPointType.AIRCRAFT_CARRIER_GROUP, ControlPointType.LHA_GROUP]:
return 0 # TODO compute heading
else:
return 0
def __str__(self):
return self.name

View File

@@ -30,10 +30,12 @@ ABBREV_NAME = {
}
CATEGORY_MAP = {
# Special cases
"CARRIER": ["CARRIER"],
"LHA": ["LHA"],
"aa": ["AA"],
"power": ["Workshop A", "Electric power box", "Garage small A"],
# Buildings
"power": ["Workshop A", "Electric power box", "Garage small A", "Farm B", "Repair workshop", "Garage B"],
"warehouse": ["Warehouse", "Hangar A"],
"fuel": ["Tank", "Tank 2", "Tank 3", "Fuel tank"],
"ammo": [".Ammunition depot", "Hangar B"],
@@ -42,6 +44,7 @@ CATEGORY_MAP = {
"factory": ["Tech combine", "Tech hangar A"],
"comms": ["TV tower", "Comms tower M"],
"oil": ["Oil platform"],
"derrick": ["Oil derrick", "Pump station", "Subsidiary structure 2"],
}

View File

@@ -1,132 +0,0 @@
"""
This utility classes provides methods to check players installed DCS environment.
TODO : add method 'is_using_open_beta', 'is_using_stable'
TODO : [NICE to have] add method to check list of installed DCS modules (could be done either through window registry, or through filesystem analysis)
"""
import winreg
import os
def is_using_dcs_steam_edition():
"""
Check if DCS World : Steam Edition version is installed on this computer
:return True if DCS Steam edition is installed,
-1 if DCS Steam Edition is registered in Steam apps but not installed,
False if never installed in Steam
"""
try:
# Note : Steam App ID for DCS World is 223750
dcs_steam_app_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Valve\\Steam\\Apps\\223750")
installed = winreg.QueryValueEx(dcs_steam_app_key, "Installed")
winreg.CloseKey(dcs_steam_app_key)
if installed[0] == 1:
return True
else:
return False
except FileNotFoundError as fnfe:
return False
def is_using_dcs_standalone_edition():
"""
Check if DCS World standalone edition is installed on this computer
:return True if Standalone is installed, False if it is not
"""
try:
dcs_path_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Eagle Dynamics\\DCS World")
winreg.CloseKey(dcs_path_key)
return True
except FileNotFoundError as fnfe:
return False
def _find_steam_directory():
"""
Get the Steam install directory for this computer from registry
:return Steam installation path
"""
try:
steam_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Valve\\Steam")
path = winreg.QueryValueEx(steam_key, "SteamPath")[0]
winreg.CloseKey(steam_key)
return path
except FileNotFoundError as fnfe:
print(fnfe)
return ""
def _get_steam_library_folders():
"""
Get the installation directory for Steam games
:return List of Steam library folders where games can be installed
"""
try:
steam_dir = _find_steam_directory()
"""
For reference here is what the vdf file is supposed to look like :
"LibraryFolders"
{
"TimeNextStatsReport" "1561832478"
"ContentStatsID" "-158337411110787451"
"1" "D:\\Games\\Steam"
"2" "E:\\Steam"
}
"""
vdf_file_location = steam_dir + os.path.sep + "steamapps" + os.path.sep + "libraryfolders.vdf"
with open(vdf_file_location) as adf_file:
paths = [l.split("\"")[3] for l in adf_file.readlines()[1:] if ':\\\\' in l]
return paths
except Exception as e:
print(e)
return []
def _find_steam_dcs_directory():
"""
Find the DCS install directory for DCS World Steam Edition
:return: Install directory as string, empty string if not found
"""
for library_folder in _get_steam_library_folders():
folder = library_folder + os.path.sep + "steamapps" + os.path.sep + "common" + os.path.sep + "DCSWorld"
if os.path.isdir(folder):
return folder + os.path.sep
return ""
def get_dcs_install_directory():
"""
Get the DCS World install directory for this computer
:return DCS World install directory
"""
if is_using_dcs_standalone_edition():
try:
dcs_path_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Eagle Dynamics\\DCS World")
path = winreg.QueryValueEx(dcs_path_key, "Path")
dcs_dir = path[0] + os.path.sep
winreg.CloseKey(dcs_path_key)
return dcs_dir
except Exception as e:
print("Couldn't detect DCS World installation folder")
return ""
elif is_using_dcs_steam_edition():
return _find_steam_dcs_directory()
else:
print("Couldn't detect any installed DCS World version")
def get_dcs_saved_games_directory():
"""
Get the save game directory for DCS World
:return: Save game directory as string
"""
return os.path.join(os.path.expanduser("~"), "Saved Games", "DCS")
if __name__ == "__main__":
print("Using STEAM Edition : " + str(is_using_dcs_steam_edition()))
print("Using Standalone Edition : " + str(is_using_dcs_standalone_edition()))
print("DCS Installation directory : " + get_dcs_install_directory())
print("DCS saved games directory : " + get_dcs_saved_games_directory())

View File

@@ -47,6 +47,8 @@ class Debriefing:
self.dead_aircraft = []
self.dead_units = []
self.dead_aaa_groups = []
self.dead_buildings = []
for aircraft in self.killed_aircrafts:
try:
@@ -70,10 +72,36 @@ class Debriefing:
except Exception as e:
print(e)
for unit in self.killed_ground_units:
for cp in game.theater.controlpoints:
print(cp.name)
print(cp.captured)
if cp.captured:
country = self.player_country_id
else:
country = self.enemy_country_id
player_unit = (country == self.player_country_id)
for i, ground_object in enumerate(cp.ground_objects):
print(unit)
print(ground_object.string_identifier)
if ground_object.matches_string_identifier(unit):
unit = DebriefingDeadUnitInfo(country, player_unit, ground_object.dcs_identifier)
self.dead_buildings.append(unit)
elif ground_object.dcs_identifier in ["AA", "CARRIER", "LHA"]:
for g in ground_object.groups:
for u in g.units:
if u.name == unit:
unit = DebriefingDeadUnitInfo(country, player_unit, db.unit_type_from_name(u.type))
self.dead_units.append(unit)
self.player_dead_aircraft = [a for a in self.dead_aircraft if a.country_id == self.player_country_id]
self.enemy_dead_aircraft = [a for a in self.dead_aircraft if a.country_id == self.enemy_country_id]
self.player_dead_units = [a for a in self.dead_units if a.country_id == self.player_country_id]
self.enemy_dead_units = [a for a in self.dead_units if a.country_id == self.enemy_country_id]
self.player_dead_buildings = [a for a in self.dead_buildings if a.country_id == self.player_country_id]
self.enemy_dead_buildings = [a for a in self.dead_buildings if a.country_id == self.enemy_country_id]
print(self.player_dead_aircraft)
print(self.enemy_dead_aircraft)
@@ -108,10 +136,27 @@ class Debriefing:
else:
self.enemy_dead_units_dict[a.type] = 1
self.player_dead_buildings_dict = {}
for a in self.player_dead_buildings:
if a.type in self.player_dead_buildings_dict.keys():
self.player_dead_buildings_dict[a.type] = self.player_dead_buildings_dict[a.type] + 1
else:
self.player_dead_buildings_dict[a.type] = 1
self.enemy_dead_buildings_dict = {}
for a in self.enemy_dead_buildings:
if a.type in self.enemy_dead_buildings_dict.keys():
self.enemy_dead_buildings_dict[a.type] = self.enemy_dead_buildings_dict[a.type] + 1
else:
self.enemy_dead_buildings_dict[a.type] = 1
print("DEBRIEFING PRE PROCESS")
print(self.player_dead_aircraft_dict)
print(self.enemy_dead_aircraft_dict)
print(self.player_dead_units_dict)
print(self.enemy_dead_units_dict)
print(self.player_dead_buildings_dict)
print(self.enemy_dead_buildings_dict)
def _poll_new_debriefing_log(callback: typing.Callable, game):

View File

@@ -0,0 +1,99 @@
import json
import os
from shutil import copyfile
import dcs
from userdata import persistency
global __dcs_saved_game_directory
global __dcs_installation_directory
PREFERENCES_FILE_PATH = "liberation_preferences.json"
def init():
global __dcs_saved_game_directory
global __dcs_installation_directory
if os.path.isfile(PREFERENCES_FILE_PATH):
try:
with(open(PREFERENCES_FILE_PATH)) as prefs:
pref_data = json.loads(prefs.read())
__dcs_saved_game_directory = pref_data["saved_game_dir"]
__dcs_installation_directory = pref_data["dcs_install_dir"]
is_first_start = False
except:
__dcs_saved_game_directory = ""
__dcs_installation_directory = ""
is_first_start = True
else:
try:
__dcs_saved_game_directory = dcs.installation.get_dcs_saved_games_directory()
if os.path.exists(__dcs_saved_game_directory + ".openbeta"):
__dcs_saved_game_directory = dcs.installation.get_dcs_saved_games_directory() + ".openbeta"
except:
__dcs_saved_game_directory = ""
try:
__dcs_installation_directory = dcs.installation.get_dcs_install_directory()
except:
__dcs_installation_directory = ""
is_first_start = True
persistency.setup(__dcs_saved_game_directory)
return is_first_start
def setup(saved_game_dir, install_dir):
global __dcs_saved_game_directory
global __dcs_installation_directory
__dcs_saved_game_directory = saved_game_dir
__dcs_installation_directory = install_dir
persistency.setup(__dcs_saved_game_directory)
def save_config():
global __dcs_saved_game_directory
global __dcs_installation_directory
pref_data = {"saved_game_dir": __dcs_saved_game_directory,
"dcs_install_dir": __dcs_installation_directory}
with(open(PREFERENCES_FILE_PATH, "w")) as prefs:
prefs.write(json.dumps(pref_data))
def get_dcs_install_directory():
global __dcs_installation_directory
return __dcs_installation_directory
def get_saved_game_dir():
global __dcs_saved_game_directory
return __dcs_saved_game_directory
def replace_mission_scripting_file():
install_dir = get_dcs_install_directory()
mission_scripting_path = os.path.join(install_dir, "Scripts", "MissionScripting.lua")
liberation_scripting_path = "./resources/scripts/MissionScripting.lua"
backup_scripting_path = "./resources/scripts/MissionScripting.original.lua"
if os.path.isfile(mission_scripting_path):
with open(mission_scripting_path, "r") as ms:
current_file_content = ms.read()
with open(liberation_scripting_path, "r") as libe_ms:
liberation_file_content = libe_ms.read()
# Save original file
if current_file_content != liberation_file_content:
copyfile(mission_scripting_path, backup_scripting_path)
# Replace DCS file
copyfile(liberation_scripting_path, mission_scripting_path)
def restore_original_mission_scripting():
install_dir = get_dcs_install_directory()
mission_scripting_path = os.path.join(install_dir, "Scripts", "MissionScripting.lua")
backup_scripting_path = "./resources/scripts/MissionScripting.original.lua"
if os.path.isfile(backup_scripting_path) and os.path.isfile(mission_scripting_path):
copyfile(backup_scripting_path, mission_scripting_path)

View File

@@ -2,9 +2,6 @@ import logging
import os
import pickle
import shutil
import sys
from dcs import installation
_dcs_saved_game_folder = None # type: str
@@ -17,12 +14,7 @@ def setup(user_folder: str):
def base_path() -> str:
global _dcs_saved_game_folder
assert _dcs_saved_game_folder
openbeta_path = _dcs_saved_game_folder + ".openbeta"
if os.path.exists(openbeta_path):
return openbeta_path # For standalone openbeta users
else:
return _dcs_saved_game_folder # For standalone stable users & steam users (any branch)
return _dcs_saved_game_folder
def _save_file() -> str: