mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7824039e3 | ||
|
|
bb9247e821 | ||
|
|
4bc04ec031 | ||
|
|
8fd91e6c5c | ||
|
|
ee137d086a | ||
|
|
fcd81850cb | ||
|
|
aa4b07d024 | ||
|
|
16a096d288 | ||
|
|
b219b2a71b | ||
|
|
ce70242c35 | ||
|
|
dec01e2611 | ||
|
|
4373d89661 | ||
|
|
c73290eebb |
32
changelog.md
32
changelog.md
@@ -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
|
||||
|
||||
15
game/db.py
15
game/db.py
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -24,7 +24,7 @@ BLUEFOR_MODERN = {
|
||||
AJS37,
|
||||
|
||||
KC_135,
|
||||
S_3B_Tanker,
|
||||
KC130,
|
||||
C_130,
|
||||
E_3A,
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ France_1995 = {
|
||||
Mirage_2000_5,
|
||||
|
||||
KC_135,
|
||||
S_3B_Tanker,
|
||||
KC130,
|
||||
C_130,
|
||||
E_3A,
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ France_2005 = {
|
||||
FA_18C_hornet, # Standing as Rafale M
|
||||
|
||||
KC_135,
|
||||
S_3B_Tanker,
|
||||
KC130,
|
||||
C_130,
|
||||
E_3A,
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ Germany_1990 = {
|
||||
F_4E,
|
||||
|
||||
KC_135,
|
||||
S_3B_Tanker,
|
||||
KC130,
|
||||
C_130,
|
||||
E_3A,
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ India_2010 = {
|
||||
Su_30,
|
||||
|
||||
KC_135,
|
||||
S_3B_Tanker,
|
||||
KC130,
|
||||
C_130,
|
||||
E_3A,
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ Israel_2000 = {
|
||||
F_4E,
|
||||
|
||||
KC_135,
|
||||
S_3B_Tanker,
|
||||
KC130,
|
||||
C_130,
|
||||
E_3A,
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ Netherlands_1990 = {
|
||||
F_5E_3,
|
||||
|
||||
KC_135,
|
||||
S_3B_Tanker,
|
||||
KC130,
|
||||
C_130,
|
||||
E_3A,
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ Spain_1990 = {
|
||||
C_101CC,
|
||||
|
||||
KC_135,
|
||||
S_3B_Tanker,
|
||||
KC130,
|
||||
C_130,
|
||||
E_3A,
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ Turkey_2005 = {
|
||||
F_4E,
|
||||
|
||||
KC_135,
|
||||
S_3B_Tanker,
|
||||
KC130,
|
||||
C_130,
|
||||
E_3A,
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ UAE_2005 = {
|
||||
F_16C_50,
|
||||
|
||||
KC_135,
|
||||
S_3B_Tanker,
|
||||
KC130,
|
||||
C_130,
|
||||
E_3A,
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ UnitedKingdom_1990 = {
|
||||
F_4E,
|
||||
|
||||
KC_135,
|
||||
S_3B_Tanker,
|
||||
KC130,
|
||||
C_130,
|
||||
E_3A,
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ USA_1944 = {
|
||||
"units": [
|
||||
P_51D,
|
||||
P_51D_30_NA,
|
||||
P_47D_30,
|
||||
SpitfireLFMkIX,
|
||||
SpitfireLFMkIXCW,
|
||||
A_20G,
|
||||
|
||||
@@ -11,7 +11,7 @@ USA_1955 = {
|
||||
P_51D,
|
||||
|
||||
KC_135,
|
||||
S_3B_Tanker,
|
||||
KC130,
|
||||
C_130,
|
||||
E_3A,
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ USA_1960 = {
|
||||
P_51D,
|
||||
|
||||
KC_135,
|
||||
S_3B_Tanker,
|
||||
KC130,
|
||||
C_130,
|
||||
E_3A,
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ USA_1965 = {
|
||||
F_4E,
|
||||
|
||||
KC_135,
|
||||
S_3B_Tanker,
|
||||
KC130,
|
||||
C_130,
|
||||
E_3A,
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ USA_1990 = {
|
||||
B_1B,
|
||||
|
||||
KC_135,
|
||||
S_3B_Tanker,
|
||||
KC130,
|
||||
C_130,
|
||||
E_3A,
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
|
||||
15
game/game.py
15
game/game.py
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -21,5 +21,6 @@ class Settings:
|
||||
perf_artillery = True
|
||||
perf_moving_units = True
|
||||
perf_infantry = True
|
||||
perf_ai_parking_start = True
|
||||
|
||||
|
||||
|
||||
277
gen/aircraft.py
277
gen/aircraft.py
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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]"
|
||||
|
||||
@@ -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"):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
80
qt_ui/windows/preferences/QLiberationFirstStartWindow.py
Normal file
80
qt_ui/windows/preferences/QLiberationFirstStartWindow.py
Normal 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><dcs_installation_directory>/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><dcs_installation_directory>/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()
|
||||
|
||||
93
qt_ui/windows/preferences/QLiberationPreferences.py
Normal file
93
qt_ui/windows/preferences/QLiberationPreferences.py
Normal 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
|
||||
|
||||
|
||||
|
||||
37
qt_ui/windows/preferences/QLiberationPreferencesWindow.py
Normal file
37
qt_ui/windows/preferences/QLiberationPreferencesWindow.py
Normal 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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pydcs>=0.9.4
|
||||
Pyside2>=5.13.0
|
||||
pyinstaller==3.5
|
||||
pyproj==2.6.1.post1
|
||||
|
||||
@@ -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,
|
||||
|
||||
85
resources/customized_payloads/P-47D-30.lua
Normal file
85
resources/customized_payloads/P-47D-30.lua
Normal 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
|
||||
@@ -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.
Binary file not shown.
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"],
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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())
|
||||
@@ -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):
|
||||
|
||||
99
userdata/liberation_install.py
Normal file
99
userdata/liberation_install.py
Normal 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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user