mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
commit
53582ba539
1
.gitignore
vendored
1
.gitignore
vendored
@ -21,4 +21,3 @@ logs/
|
|||||||
qt_ui/logs/liberation.log
|
qt_ui/logs/liberation.log
|
||||||
|
|
||||||
*.psd
|
*.psd
|
||||||
resources/scripts/plugins/*
|
|
||||||
|
|||||||
6
.gitmodules
vendored
6
.gitmodules
vendored
@ -2,3 +2,9 @@
|
|||||||
path = pydcs
|
path = pydcs
|
||||||
url = https://github.com/pydcs/dcs
|
url = https://github.com/pydcs/dcs
|
||||||
branch = master
|
branch = master
|
||||||
|
[submodule "plugin/veaf"]
|
||||||
|
path = plugin/veaf
|
||||||
|
url = https://github.com/VEAF/dcs-liberation-veaf-framework
|
||||||
|
[submodule "resources/plugins/veaf"]
|
||||||
|
path = resources/plugins/veaf
|
||||||
|
url = https://github.com/VEAF/dcs-liberation-veaf-framework
|
||||||
|
|||||||
@ -28,6 +28,7 @@ from .event.event import Event, UnitsDeliveryEvent
|
|||||||
from .event.frontlineattack import FrontlineAttackEvent
|
from .event.frontlineattack import FrontlineAttackEvent
|
||||||
from .infos.information import Information
|
from .infos.information import Information
|
||||||
from .settings import Settings
|
from .settings import Settings
|
||||||
|
from plugin import LuaPluginManager
|
||||||
from .weather import Conditions, TimeOfDay
|
from .weather import Conditions, TimeOfDay
|
||||||
|
|
||||||
COMMISION_UNIT_VARIETY = 4
|
COMMISION_UNIT_VARIETY = 4
|
||||||
@ -224,6 +225,10 @@ class Game:
|
|||||||
def on_load(self) -> None:
|
def on_load(self) -> None:
|
||||||
ObjectiveDistanceCache.set_theater(self.theater)
|
ObjectiveDistanceCache.set_theater(self.theater)
|
||||||
|
|
||||||
|
# set the settings in all plugins
|
||||||
|
for plugin in LuaPluginManager().getPlugins():
|
||||||
|
plugin.setSettings(self.settings)
|
||||||
|
|
||||||
# Save game compatibility.
|
# Save game compatibility.
|
||||||
|
|
||||||
# TODO: Remove in 2.3.
|
# TODO: Remove in 2.3.
|
||||||
|
|||||||
@ -14,7 +14,7 @@ from dcs.translation import String
|
|||||||
from dcs.triggers import TriggerStart
|
from dcs.triggers import TriggerStart
|
||||||
from dcs.unittype import UnitType
|
from dcs.unittype import UnitType
|
||||||
|
|
||||||
from gen import Conflict, VisualGenerator
|
from gen import Conflict, VisualGenerator, FlightType
|
||||||
from gen.aircraft import AIRCRAFT_DATA, AircraftConflictGenerator, FlightData
|
from gen.aircraft import AIRCRAFT_DATA, AircraftConflictGenerator, FlightData
|
||||||
from gen.airfields import AIRFIELD_DATA
|
from gen.airfields import AIRFIELD_DATA
|
||||||
from gen.airsupportgen import AirSupport, AirSupportConflictGenerator
|
from gen.airsupportgen import AirSupport, AirSupportConflictGenerator
|
||||||
@ -31,7 +31,7 @@ from gen.triggergen import TRIGGER_RADIUS_MEDIUM, TriggersGenerator
|
|||||||
from theater import ControlPoint
|
from theater import ControlPoint
|
||||||
from .. import db
|
from .. import db
|
||||||
from ..debriefing import Debriefing
|
from ..debriefing import Debriefing
|
||||||
|
from plugin import LuaPluginManager
|
||||||
|
|
||||||
class Operation:
|
class Operation:
|
||||||
attackers_starting_position = None # type: db.StartingPosition
|
attackers_starting_position = None # type: db.StartingPosition
|
||||||
@ -74,6 +74,7 @@ class Operation:
|
|||||||
self.departure_cp = departure_cp
|
self.departure_cp = departure_cp
|
||||||
self.to_cp = to_cp
|
self.to_cp = to_cp
|
||||||
self.is_quick = False
|
self.is_quick = False
|
||||||
|
self.listOfPluginsScripts = []
|
||||||
|
|
||||||
def units_of(self, country_name: str) -> List[UnitType]:
|
def units_of(self, country_name: str) -> List[UnitType]:
|
||||||
return []
|
return []
|
||||||
@ -132,6 +133,34 @@ class Operation:
|
|||||||
else:
|
else:
|
||||||
self.defenders_starting_position = None
|
self.defenders_starting_position = None
|
||||||
|
|
||||||
|
def injectLuaTrigger(self, luascript, comment = "LUA script"):
|
||||||
|
trigger = TriggerStart(comment=comment)
|
||||||
|
trigger.add_action(DoScript(String(luascript)))
|
||||||
|
self.current_mission.triggerrules.triggers.append(trigger)
|
||||||
|
|
||||||
|
def bypassPluginScript(self, pluginName, scriptFileMnemonic):
|
||||||
|
self.listOfPluginsScripts.append(scriptFileMnemonic)
|
||||||
|
|
||||||
|
def injectPluginScript(self, pluginName, scriptFile, scriptFileMnemonic):
|
||||||
|
if not scriptFileMnemonic in self.listOfPluginsScripts:
|
||||||
|
self.listOfPluginsScripts.append(scriptFileMnemonic)
|
||||||
|
|
||||||
|
plugin_path = Path("./resources/plugins",pluginName)
|
||||||
|
|
||||||
|
if scriptFile != None:
|
||||||
|
scriptFile_path = Path(plugin_path, scriptFile)
|
||||||
|
if scriptFile_path.exists():
|
||||||
|
trigger = TriggerStart(comment="Load " + scriptFileMnemonic)
|
||||||
|
filename = scriptFile_path.resolve()
|
||||||
|
fileref = self.current_mission.map_resource.add_resource_file(filename)
|
||||||
|
trigger.add_action(DoScriptFile(fileref))
|
||||||
|
self.current_mission.triggerrules.triggers.append(trigger)
|
||||||
|
else:
|
||||||
|
logging.error(f"Cannot find script file {scriptFile} for plugin {pluginName}")
|
||||||
|
|
||||||
|
else:
|
||||||
|
logging.debug(f"Skipping script file {scriptFile} for plugin {pluginName}")
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
radio_registry = RadioRegistry()
|
radio_registry = RadioRegistry()
|
||||||
tacan_registry = TacanRegistry()
|
tacan_registry = TacanRegistry()
|
||||||
@ -254,98 +283,181 @@ class Operation:
|
|||||||
if self.game.settings.perf_smoke_gen:
|
if self.game.settings.perf_smoke_gen:
|
||||||
visualgen.generate()
|
visualgen.generate()
|
||||||
|
|
||||||
# Inject Plugins Lua Scripts
|
luaData = {}
|
||||||
listOfPluginsScripts = []
|
luaData["AircraftCarriers"] = {}
|
||||||
plugin_file_path = Path("./resources/scripts/plugins/__plugins.lst")
|
luaData["Tankers"] = {}
|
||||||
if plugin_file_path.exists():
|
luaData["AWACs"] = {}
|
||||||
for line in plugin_file_path.read_text().splitlines():
|
luaData["JTACs"] = {}
|
||||||
name = line.strip()
|
luaData["TargetPoints"] = {}
|
||||||
if not name.startswith( '#' ):
|
|
||||||
trigger = TriggerStart(comment="Load " + name)
|
|
||||||
listOfPluginsScripts.append(name)
|
|
||||||
fileref = self.current_mission.map_resource.add_resource_file("./resources/scripts/plugins/" + name)
|
|
||||||
trigger.add_action(DoScriptFile(fileref))
|
|
||||||
self.current_mission.triggerrules.triggers.append(trigger)
|
|
||||||
else:
|
|
||||||
logging.info(
|
|
||||||
f"Not loading plugins, {plugin_file_path} does not exist")
|
|
||||||
|
|
||||||
# Inject Mist Script if not done already in the plugins
|
self.assign_channels_to_flights(airgen.flights,
|
||||||
if not "mist.lua" in listOfPluginsScripts and not "mist_4_3_74.lua" in listOfPluginsScripts: # don't load the script twice
|
airsupportgen.air_support)
|
||||||
trigger = TriggerStart(comment="Load Mist Lua framework")
|
|
||||||
fileref = self.current_mission.map_resource.add_resource_file("./resources/scripts/mist_4_3_74.lua")
|
|
||||||
trigger.add_action(DoScriptFile(fileref))
|
|
||||||
self.current_mission.triggerrules.triggers.append(trigger)
|
|
||||||
|
|
||||||
# Inject JSON library if not done already in the plugins
|
kneeboard_generator = KneeboardGenerator(self.current_mission)
|
||||||
if not "json.lua" in listOfPluginsScripts : # don't load the script twice
|
for dynamic_runway in groundobjectgen.runways.values():
|
||||||
trigger = TriggerStart(comment="Load JSON Lua library")
|
self.briefinggen.add_dynamic_runway(dynamic_runway)
|
||||||
fileref = self.current_mission.map_resource.add_resource_file("./resources/scripts/json.lua")
|
|
||||||
trigger.add_action(DoScriptFile(fileref))
|
for tanker in airsupportgen.air_support.tankers:
|
||||||
self.current_mission.triggerrules.triggers.append(trigger)
|
self.briefinggen.add_tanker(tanker)
|
||||||
|
kneeboard_generator.add_tanker(tanker)
|
||||||
|
luaData["Tankers"][tanker.callsign] = {
|
||||||
|
"dcsGroupName": tanker.dcsGroupName,
|
||||||
|
"callsign": tanker.callsign,
|
||||||
|
"variant": tanker.variant,
|
||||||
|
"radio": tanker.freq.mhz,
|
||||||
|
"tacan": str(tanker.tacan.number) + tanker.tacan.band.name
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.is_awacs_enabled:
|
||||||
|
for awacs in airsupportgen.air_support.awacs:
|
||||||
|
self.briefinggen.add_awacs(awacs)
|
||||||
|
kneeboard_generator.add_awacs(awacs)
|
||||||
|
luaData["AWACs"][awacs.callsign] = {
|
||||||
|
"dcsGroupName": awacs.dcsGroupName,
|
||||||
|
"callsign": awacs.callsign,
|
||||||
|
"radio": awacs.freq.mhz
|
||||||
|
}
|
||||||
|
|
||||||
|
for jtac in jtacs:
|
||||||
|
self.briefinggen.add_jtac(jtac)
|
||||||
|
kneeboard_generator.add_jtac(jtac)
|
||||||
|
luaData["JTACs"][jtac.callsign] = {
|
||||||
|
"dcsGroupName": jtac.dcsGroupName,
|
||||||
|
"callsign": jtac.callsign,
|
||||||
|
"zone": jtac.region,
|
||||||
|
"dcsUnit": jtac.unit_name,
|
||||||
|
"laserCode": jtac.code
|
||||||
|
}
|
||||||
|
|
||||||
|
for flight in airgen.flights:
|
||||||
|
self.briefinggen.add_flight(flight)
|
||||||
|
kneeboard_generator.add_flight(flight)
|
||||||
|
if flight.friendly and flight.flight_type in [FlightType.ANTISHIP, FlightType.DEAD, FlightType.SEAD, FlightType.STRIKE]:
|
||||||
|
flightType = flight.flight_type.name
|
||||||
|
flightTarget = flight.targetPoint
|
||||||
|
if flightTarget:
|
||||||
|
flightTargetName = None
|
||||||
|
flightTargetType = None
|
||||||
|
if hasattr(flightTarget, 'obj_name'):
|
||||||
|
flightTargetName = flightTarget.obj_name
|
||||||
|
flightTargetType = flightType + f" TGT ({flightTarget.category})"
|
||||||
|
elif hasattr(flightTarget, 'name'):
|
||||||
|
flightTargetName = flightTarget.name
|
||||||
|
flightTargetType = flightType + " TGT (Airbase)"
|
||||||
|
luaData["TargetPoints"][flightTargetName] = {
|
||||||
|
"name": flightTargetName,
|
||||||
|
"type": flightTargetType,
|
||||||
|
"position": { "x": flightTarget.position.x, "y": flightTarget.position.y}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
self.briefinggen.generate()
|
||||||
|
kneeboard_generator.generate()
|
||||||
|
|
||||||
# Inject Ciribob's JTACAutoLase if not done already in the plugins
|
|
||||||
if not "JTACAutoLase.lua" in listOfPluginsScripts : # don't load the script twice
|
|
||||||
trigger = TriggerStart(comment="Load JTACAutoLase.lua script")
|
|
||||||
fileref = self.current_mission.map_resource.add_resource_file("./resources/scripts/JTACAutoLase.lua")
|
|
||||||
trigger.add_action(DoScriptFile(fileref))
|
|
||||||
self.current_mission.triggerrules.triggers.append(trigger)
|
|
||||||
|
|
||||||
# set a LUA table with data from Liberation that we want to set
|
# set a LUA table with data from Liberation that we want to set
|
||||||
# at the moment it contains Liberation's install path, and an overridable definition for the JTACAutoLase function
|
# at the moment it contains Liberation's install path, and an overridable definition for the JTACAutoLase function
|
||||||
# later, we'll add data about the units and points having been generated, in order to facilitate the configuration of the plugin lua scripts
|
# later, we'll add data about the units and points having been generated, in order to facilitate the configuration of the plugin lua scripts
|
||||||
state_location = "[[" + os.path.abspath("state.json") + "]]"
|
state_location = "[[" + os.path.abspath("state.json") + "]]"
|
||||||
lua = """
|
lua = """
|
||||||
-- setting configuration table
|
-- setting configuration table
|
||||||
env.info("DCSLiberation|: setting configuration table")
|
env.info("DCSLiberation|: setting configuration table")
|
||||||
|
|
||||||
-- all data in this table is overridable.
|
-- all data in this table is overridable.
|
||||||
dcsLiberation = {}
|
dcsLiberation = {}
|
||||||
|
|
||||||
-- the base location for state.json; if non-existent, it'll be replaced with LIBERATION_EXPORT_DIR, TEMP, or DCS working directory
|
-- the base location for state.json; if non-existent, it'll be replaced with LIBERATION_EXPORT_DIR, TEMP, or DCS working directory
|
||||||
dcsLiberation.installPath=""" + state_location + """
|
dcsLiberation.installPath=""" + state_location + """
|
||||||
|
|
||||||
-- you can override dcsLiberation.JTACAutoLase to make it use your own function ; it will be called with these parameters : ({jtac.unit_name}, {jtac.code}, {smoke}, 'vehicle') for all JTACs
|
"""
|
||||||
if ctld then
|
# Process the tankers
|
||||||
dcsLiberation.JTACAutoLase=ctld.JTACAutoLase
|
lua += """
|
||||||
elseif JTACAutoLase then
|
|
||||||
dcsLiberation.JTACAutoLase=JTACAutoLase
|
-- list the tankers generated by Liberation
|
||||||
end
|
dcsLiberation.Tankers = {
|
||||||
|
"""
|
||||||
|
for key in luaData["Tankers"]:
|
||||||
|
data = luaData["Tankers"][key]
|
||||||
|
dcsGroupName= data["dcsGroupName"]
|
||||||
|
callsign = data["callsign"]
|
||||||
|
variant = data["variant"]
|
||||||
|
tacan = data["tacan"]
|
||||||
|
radio = data["radio"]
|
||||||
|
lua += f" {{dcsGroupName='{dcsGroupName}', callsign='{callsign}', variant='{variant}', tacan='{tacan}', radio='{radio}' }}, \n"
|
||||||
|
#lua += f" {{name='{dcsGroupName}', description='{callsign} ({variant})', information='Tacan:{tacan} Radio:{radio}' }}, \n"
|
||||||
|
lua += "}"
|
||||||
|
|
||||||
|
# Process the AWACSes
|
||||||
|
lua += """
|
||||||
|
|
||||||
|
-- list the AWACs generated by Liberation
|
||||||
|
dcsLiberation.AWACs = {
|
||||||
|
"""
|
||||||
|
for key in luaData["AWACs"]:
|
||||||
|
data = luaData["AWACs"][key]
|
||||||
|
dcsGroupName= data["dcsGroupName"]
|
||||||
|
callsign = data["callsign"]
|
||||||
|
radio = data["radio"]
|
||||||
|
lua += f" {{dcsGroupName='{dcsGroupName}', callsign='{callsign}', radio='{radio}' }}, \n"
|
||||||
|
#lua += f" {{name='{dcsGroupName}', description='{callsign} (AWACS)', information='Radio:{radio}' }}, \n"
|
||||||
|
lua += "}"
|
||||||
|
|
||||||
|
# Process the JTACs
|
||||||
|
lua += """
|
||||||
|
|
||||||
|
-- list the JTACs generated by Liberation
|
||||||
|
dcsLiberation.JTACs = {
|
||||||
|
"""
|
||||||
|
for key in luaData["JTACs"]:
|
||||||
|
data = luaData["JTACs"][key]
|
||||||
|
dcsGroupName= data["dcsGroupName"]
|
||||||
|
callsign = data["callsign"]
|
||||||
|
zone = data["zone"]
|
||||||
|
laserCode = data["laserCode"]
|
||||||
|
dcsUnit = data["dcsUnit"]
|
||||||
|
lua += f" {{dcsGroupName='{dcsGroupName}', callsign='{callsign}', zone='{zone}', laserCode='{laserCode}', dcsUnit='{dcsUnit}' }}, \n"
|
||||||
|
#lua += f" {{name='{dcsGroupName}', description='JTAC {callsign} ', information='Laser:{laserCode}', jtac={laserCode} }}, \n"
|
||||||
|
lua += "}"
|
||||||
|
|
||||||
|
# Process the Target Points
|
||||||
|
lua += """
|
||||||
|
|
||||||
|
-- list the target points generated by Liberation
|
||||||
|
dcsLiberation.TargetPoints = {
|
||||||
|
"""
|
||||||
|
for key in luaData["TargetPoints"]:
|
||||||
|
data = luaData["TargetPoints"][key]
|
||||||
|
name = data["name"]
|
||||||
|
pointType = data["type"]
|
||||||
|
positionX = data["position"]["x"]
|
||||||
|
positionY = data["position"]["y"]
|
||||||
|
lua += f" {{name='{name}', pointType='{pointType}', positionX='{positionX}', positionY='{positionY}' }}, \n"
|
||||||
|
#lua += f" {{name='{pointType} {name}', point{{x={positionX}, z={positionY} }} }}, \n"
|
||||||
|
lua += "}"
|
||||||
|
|
||||||
|
lua += """
|
||||||
|
|
||||||
|
-- list the airbases generated by Liberation
|
||||||
|
-- dcsLiberation.Airbases = {}
|
||||||
|
|
||||||
|
-- list the aircraft carriers generated by Liberation
|
||||||
|
-- dcsLiberation.Carriers = {}
|
||||||
|
|
||||||
|
-- later, we'll add more data to the table
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
-- later, we'll add more data to the table
|
|
||||||
--dcsLiberation.POIs = {}
|
|
||||||
--dcsLiberation.BASEs = {}
|
|
||||||
--dcsLiberation.JTACs = {}
|
|
||||||
"""
|
|
||||||
|
|
||||||
trigger = TriggerStart(comment="Set DCS Liberation data")
|
trigger = TriggerStart(comment="Set DCS Liberation data")
|
||||||
trigger.add_action(DoScript(String(lua)))
|
trigger.add_action(DoScript(String(lua)))
|
||||||
self.current_mission.triggerrules.triggers.append(trigger)
|
self.current_mission.triggerrules.triggers.append(trigger)
|
||||||
|
|
||||||
# Inject DCS-Liberation script if not done already in the plugins
|
# Inject Plugins Lua Scripts and data
|
||||||
if not "dcs_liberation.lua" in listOfPluginsScripts : # don't load the script twice
|
self.listOfPluginsScripts = []
|
||||||
trigger = TriggerStart(comment="Load DCS Liberation script")
|
|
||||||
fileref = self.current_mission.map_resource.add_resource_file("./resources/scripts/dcs_liberation.lua")
|
|
||||||
trigger.add_action(DoScriptFile(fileref))
|
|
||||||
self.current_mission.triggerrules.triggers.append(trigger)
|
|
||||||
|
|
||||||
# add a configuration for JTACAutoLase and start lasing for all JTACs
|
for plugin in LuaPluginManager().getPlugins():
|
||||||
smoke = "true"
|
plugin.injectScripts(self)
|
||||||
if hasattr(self.game.settings, "jtac_smoke_on"):
|
plugin.injectConfiguration(self)
|
||||||
if not self.game.settings.jtac_smoke_on:
|
|
||||||
smoke = "false"
|
|
||||||
|
|
||||||
lua = """
|
|
||||||
-- setting and starting JTACs
|
|
||||||
env.info("DCSLiberation|: setting and starting JTACs")
|
|
||||||
"""
|
|
||||||
|
|
||||||
for jtac in jtacs:
|
|
||||||
lua += f"if dcsLiberation.JTACAutoLase then dcsLiberation.JTACAutoLase('{jtac.unit_name}', {jtac.code}, {smoke}, 'vehicle') end\n"
|
|
||||||
|
|
||||||
trigger = TriggerStart(comment="Start JTACs")
|
|
||||||
trigger.add_action(DoScript(String(lua)))
|
|
||||||
self.current_mission.triggerrules.triggers.append(trigger)
|
|
||||||
|
|
||||||
self.assign_channels_to_flights(airgen.flights,
|
self.assign_channels_to_flights(airgen.flights,
|
||||||
airsupportgen.air_support)
|
airsupportgen.air_support)
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
from plugin import LuaPluginManager
|
||||||
|
|
||||||
class Settings:
|
class Settings:
|
||||||
|
|
||||||
@ -24,8 +25,6 @@ class Settings:
|
|||||||
self.sams = True # Legacy parameter do not use
|
self.sams = True # Legacy parameter do not use
|
||||||
self.cold_start = False # Legacy parameter do not use
|
self.cold_start = False # Legacy parameter do not use
|
||||||
self.version = None
|
self.version = None
|
||||||
self.include_jtac_if_available = True
|
|
||||||
self.jtac_smoke_on = True
|
|
||||||
|
|
||||||
# Performance oriented
|
# Performance oriented
|
||||||
self.perf_red_alert_state = True
|
self.perf_red_alert_state = True
|
||||||
@ -40,6 +39,12 @@ class Settings:
|
|||||||
self.perf_culling = False
|
self.perf_culling = False
|
||||||
self.perf_culling_distance = 100
|
self.perf_culling_distance = 100
|
||||||
|
|
||||||
|
# LUA Plugins system
|
||||||
|
self.plugins = {}
|
||||||
|
for plugin in LuaPluginManager().getPlugins():
|
||||||
|
plugin.setSettings(self)
|
||||||
|
|
||||||
|
|
||||||
# Cheating
|
# Cheating
|
||||||
self.show_red_ato = False
|
self.show_red_ato = False
|
||||||
|
|
||||||
|
|||||||
@ -237,11 +237,14 @@ class FlightData:
|
|||||||
#: Map of radio frequencies to their assigned radio and channel, if any.
|
#: Map of radio frequencies to their assigned radio and channel, if any.
|
||||||
frequency_to_channel_map: Dict[RadioFrequency, ChannelAssignment]
|
frequency_to_channel_map: Dict[RadioFrequency, ChannelAssignment]
|
||||||
|
|
||||||
|
#: Data concerning the target of a CAS/Strike/SEAD flight, or None else
|
||||||
|
targetPoint = None
|
||||||
|
|
||||||
def __init__(self, flight_type: FlightType, units: List[FlyingUnit],
|
def __init__(self, flight_type: FlightType, units: List[FlyingUnit],
|
||||||
size: int, friendly: bool, departure_delay: int,
|
size: int, friendly: bool, departure_delay: int,
|
||||||
departure: RunwayData, arrival: RunwayData,
|
departure: RunwayData, arrival: RunwayData,
|
||||||
divert: Optional[RunwayData], waypoints: List[FlightWaypoint],
|
divert: Optional[RunwayData], waypoints: List[FlightWaypoint],
|
||||||
intra_flight_channel: RadioFrequency) -> None:
|
intra_flight_channel: RadioFrequency, targetPoint: Optional) -> None:
|
||||||
self.flight_type = flight_type
|
self.flight_type = flight_type
|
||||||
self.units = units
|
self.units = units
|
||||||
self.size = size
|
self.size = size
|
||||||
@ -254,6 +257,7 @@ class FlightData:
|
|||||||
self.intra_flight_channel = intra_flight_channel
|
self.intra_flight_channel = intra_flight_channel
|
||||||
self.frequency_to_channel_map = {}
|
self.frequency_to_channel_map = {}
|
||||||
self.callsign = create_group_callsign_from_unit(self.units[0])
|
self.callsign = create_group_callsign_from_unit(self.units[0])
|
||||||
|
self.targetPoint = targetPoint
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def client_units(self) -> List[FlyingUnit]:
|
def client_units(self) -> List[FlyingUnit]:
|
||||||
@ -645,7 +649,8 @@ class AircraftConflictGenerator:
|
|||||||
divert=None,
|
divert=None,
|
||||||
# Waypoints are added later, after they've had their TOTs set.
|
# Waypoints are added later, after they've had their TOTs set.
|
||||||
waypoints=[],
|
waypoints=[],
|
||||||
intra_flight_channel=channel
|
intra_flight_channel=channel,
|
||||||
|
targetPoint=flight.targetPoint,
|
||||||
))
|
))
|
||||||
|
|
||||||
# Special case so Su 33 carrier take off
|
# Special case so Su 33 carrier take off
|
||||||
|
|||||||
@ -30,6 +30,7 @@ AWACS_ALT = 13000
|
|||||||
@dataclass
|
@dataclass
|
||||||
class AwacsInfo:
|
class AwacsInfo:
|
||||||
"""AWACS information for the kneeboard."""
|
"""AWACS information for the kneeboard."""
|
||||||
|
dcsGroupName: str
|
||||||
callsign: str
|
callsign: str
|
||||||
freq: RadioFrequency
|
freq: RadioFrequency
|
||||||
|
|
||||||
@ -37,6 +38,7 @@ class AwacsInfo:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class TankerInfo:
|
class TankerInfo:
|
||||||
"""Tanker information for the kneeboard."""
|
"""Tanker information for the kneeboard."""
|
||||||
|
dcsGroupName: str
|
||||||
callsign: str
|
callsign: str
|
||||||
variant: str
|
variant: str
|
||||||
freq: RadioFrequency
|
freq: RadioFrequency
|
||||||
@ -116,7 +118,7 @@ class AirSupportConflictGenerator:
|
|||||||
tanker_group.points[0].tasks.append(SetInvisibleCommand(True))
|
tanker_group.points[0].tasks.append(SetInvisibleCommand(True))
|
||||||
tanker_group.points[0].tasks.append(SetImmortalCommand(True))
|
tanker_group.points[0].tasks.append(SetImmortalCommand(True))
|
||||||
|
|
||||||
self.air_support.tankers.append(TankerInfo(callsign, variant, freq, tacan))
|
self.air_support.tankers.append(TankerInfo(str(tanker_group.name), callsign, variant, freq, tacan))
|
||||||
|
|
||||||
if is_awacs_enabled:
|
if is_awacs_enabled:
|
||||||
try:
|
try:
|
||||||
@ -138,6 +140,6 @@ class AirSupportConflictGenerator:
|
|||||||
awacs_flight.points[0].tasks.append(SetImmortalCommand(True))
|
awacs_flight.points[0].tasks.append(SetImmortalCommand(True))
|
||||||
|
|
||||||
self.air_support.awacs.append(AwacsInfo(
|
self.air_support.awacs.append(AwacsInfo(
|
||||||
callsign_for_support_unit(awacs_flight), freq))
|
str(awacs_flight.name), callsign_for_support_unit(awacs_flight), freq))
|
||||||
except:
|
except:
|
||||||
print("No AWACS for faction")
|
print("No AWACS for faction")
|
||||||
@ -34,6 +34,7 @@ from gen.ground_forces.ai_ground_planner import (
|
|||||||
from .callsigns import callsign_for_support_unit
|
from .callsigns import callsign_for_support_unit
|
||||||
from .conflictgen import Conflict
|
from .conflictgen import Conflict
|
||||||
from .ground_forces.combat_stance import CombatStance
|
from .ground_forces.combat_stance import CombatStance
|
||||||
|
from plugin import LuaPluginManager
|
||||||
|
|
||||||
SPREAD_DISTANCE_FACTOR = 0.1, 0.3
|
SPREAD_DISTANCE_FACTOR = 0.1, 0.3
|
||||||
SPREAD_DISTANCE_SIZE_FACTOR = 0.1
|
SPREAD_DISTANCE_SIZE_FACTOR = 0.1
|
||||||
@ -54,6 +55,7 @@ RANDOM_OFFSET_ATTACK = 250
|
|||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class JtacInfo:
|
class JtacInfo:
|
||||||
"""JTAC information."""
|
"""JTAC information."""
|
||||||
|
dcsGroupName: str
|
||||||
unit_name: str
|
unit_name: str
|
||||||
callsign: str
|
callsign: str
|
||||||
region: str
|
region: str
|
||||||
@ -138,7 +140,9 @@ class GroundConflictGenerator:
|
|||||||
self.plan_action_for_groups(self.enemy_stance, enemy_groups, player_groups, self.conflict.heading - 90, self.conflict.to_cp, self.conflict.from_cp)
|
self.plan_action_for_groups(self.enemy_stance, enemy_groups, player_groups, self.conflict.heading - 90, self.conflict.to_cp, self.conflict.from_cp)
|
||||||
|
|
||||||
# Add JTAC
|
# Add JTAC
|
||||||
if "has_jtac" in self.game.player_faction and self.game.player_faction["has_jtac"] and self.game.settings.include_jtac_if_available:
|
jtacPlugin = LuaPluginManager().getPlugin("jtacautolase")
|
||||||
|
useJTAC = jtacPlugin and jtacPlugin.isEnabled()
|
||||||
|
if "has_jtac" in self.game.player_faction and self.game.player_faction["has_jtac"] and useJTAC:
|
||||||
n = "JTAC" + str(self.conflict.from_cp.id) + str(self.conflict.to_cp.id)
|
n = "JTAC" + str(self.conflict.from_cp.id) + str(self.conflict.to_cp.id)
|
||||||
code = 1688 - len(self.jtacs)
|
code = 1688 - len(self.jtacs)
|
||||||
|
|
||||||
@ -158,7 +162,7 @@ class GroundConflictGenerator:
|
|||||||
frontline = f"Frontline {self.conflict.from_cp.name}/{self.conflict.to_cp.name}"
|
frontline = f"Frontline {self.conflict.from_cp.name}/{self.conflict.to_cp.name}"
|
||||||
# Note: Will need to change if we ever add ground based JTAC.
|
# Note: Will need to change if we ever add ground based JTAC.
|
||||||
callsign = callsign_for_support_unit(jtac)
|
callsign = callsign_for_support_unit(jtac)
|
||||||
self.jtacs.append(JtacInfo(n, callsign, frontline, str(code)))
|
self.jtacs.append(JtacInfo(str(jtac.name), n, callsign, frontline, str(code)))
|
||||||
|
|
||||||
def gen_infantry_group_for_group(self, group, is_player, side:Country, forward_heading):
|
def gen_infantry_group_for_group(self, group, is_player, side:Country, forward_heading):
|
||||||
|
|
||||||
|
|||||||
@ -209,6 +209,7 @@ class PackageBuilder:
|
|||||||
flight = Flight(aircraft, plan.num_aircraft, airfield, plan.task,
|
flight = Flight(aircraft, plan.num_aircraft, airfield, plan.task,
|
||||||
self.start_type)
|
self.start_type)
|
||||||
self.package.add_flight(flight)
|
self.package.add_flight(flight)
|
||||||
|
flight.targetPoint = self.package.target
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def build(self) -> Package:
|
def build(self) -> Package:
|
||||||
@ -221,7 +222,7 @@ class PackageBuilder:
|
|||||||
for flight in flights:
|
for flight in flights:
|
||||||
self.global_inventory.return_from_flight(flight)
|
self.global_inventory.return_from_flight(flight)
|
||||||
self.package.remove_flight(flight)
|
self.package.remove_flight(flight)
|
||||||
|
flight.targetPoint = None
|
||||||
|
|
||||||
class ObjectiveFinder:
|
class ObjectiveFinder:
|
||||||
"""Identifies potential objectives for the mission planner."""
|
"""Identifies potential objectives for the mission planner."""
|
||||||
|
|||||||
@ -137,6 +137,7 @@ class Flight:
|
|||||||
use_custom_loadout = False
|
use_custom_loadout = False
|
||||||
preset_loadout_name = ""
|
preset_loadout_name = ""
|
||||||
group = False # Contains DCS Mission group data after mission has been generated
|
group = False # Contains DCS Mission group data after mission has been generated
|
||||||
|
targetPoint = None # Contains either None or a Strike/SEAD target point location
|
||||||
|
|
||||||
def __init__(self, unit_type: UnitType, count: int, from_cp: ControlPoint,
|
def __init__(self, unit_type: UnitType, count: int, from_cp: ControlPoint,
|
||||||
flight_type: FlightType, start_type: str) -> None:
|
flight_type: FlightType, start_type: str) -> None:
|
||||||
|
|||||||
2
plugin/__init__.py
Normal file
2
plugin/__init__.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
from .luaplugin import LuaPlugin
|
||||||
|
from .manager import LuaPluginManager
|
||||||
199
plugin/luaplugin.py
Normal file
199
plugin/luaplugin.py
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
from typing import List
|
||||||
|
from pathlib import Path
|
||||||
|
from PySide2.QtCore import QSize, Qt, QItemSelectionModel, QPoint
|
||||||
|
from PySide2.QtWidgets import QLabel, QDialog, QGridLayout, QListView, QStackedLayout, QComboBox, QWidget, \
|
||||||
|
QAbstractItemView, QPushButton, QGroupBox, QCheckBox, QVBoxLayout, QSpinBox
|
||||||
|
import json
|
||||||
|
|
||||||
|
class LuaPluginWorkOrder():
|
||||||
|
|
||||||
|
def __init__(self, parent, filename:str, mnemonic:str, disable:bool):
|
||||||
|
self.filename = filename
|
||||||
|
self.mnemonic = mnemonic
|
||||||
|
self.disable = disable
|
||||||
|
self.parent = parent
|
||||||
|
|
||||||
|
def work(self, mnemonic:str, operation):
|
||||||
|
if self.disable:
|
||||||
|
operation.bypassPluginScript(self.parent.mnemonic, self.mnemonic)
|
||||||
|
else:
|
||||||
|
operation.injectPluginScript(self.parent.mnemonic, self.filename, self.mnemonic)
|
||||||
|
|
||||||
|
class LuaPluginSpecificOption():
|
||||||
|
|
||||||
|
def __init__(self, parent, mnemonic:str, nameInUI:str, defaultValue:bool):
|
||||||
|
self.mnemonic = mnemonic
|
||||||
|
self.nameInUI = nameInUI
|
||||||
|
self.defaultValue = defaultValue
|
||||||
|
self.parent = parent
|
||||||
|
|
||||||
|
class LuaPlugin():
|
||||||
|
NAME_IN_SETTINGS_BASE:str = "plugins."
|
||||||
|
|
||||||
|
def __init__(self, jsonFilename:str):
|
||||||
|
self.mnemonic:str = None
|
||||||
|
self.skipUI:bool = False
|
||||||
|
self.nameInUI:str = None
|
||||||
|
self.nameInSettings:str = None
|
||||||
|
self.defaultValue:bool = False
|
||||||
|
self.specificOptions = []
|
||||||
|
self.scriptsWorkOrders: List[LuaPluginWorkOrder] = None
|
||||||
|
self.configurationWorkOrders: List[LuaPluginWorkOrder] = None
|
||||||
|
self.initFromJson(jsonFilename)
|
||||||
|
self.enabled = self.defaultValue
|
||||||
|
self.settings = None
|
||||||
|
|
||||||
|
def initFromJson(self, jsonFilename:str):
|
||||||
|
jsonFile:Path = Path(jsonFilename)
|
||||||
|
if jsonFile.exists():
|
||||||
|
jsonData = json.loads(jsonFile.read_text())
|
||||||
|
self.mnemonic = jsonData.get("mnemonic")
|
||||||
|
self.skipUI = jsonData.get("skipUI", False)
|
||||||
|
self.nameInUI = jsonData.get("nameInUI")
|
||||||
|
self.nameInSettings = LuaPlugin.NAME_IN_SETTINGS_BASE + self.mnemonic
|
||||||
|
self.defaultValue = jsonData.get("defaultValue", False)
|
||||||
|
self.specificOptions = []
|
||||||
|
for jsonSpecificOption in jsonData.get("specificOptions"):
|
||||||
|
mnemonic = jsonSpecificOption.get("mnemonic")
|
||||||
|
nameInUI = jsonSpecificOption.get("nameInUI", mnemonic)
|
||||||
|
defaultValue = jsonSpecificOption.get("defaultValue")
|
||||||
|
self.specificOptions.append(LuaPluginSpecificOption(self, mnemonic, nameInUI, defaultValue))
|
||||||
|
self.scriptsWorkOrders = []
|
||||||
|
for jsonWorkOrder in jsonData.get("scriptsWorkOrders"):
|
||||||
|
file = jsonWorkOrder.get("file")
|
||||||
|
mnemonic = jsonWorkOrder.get("mnemonic")
|
||||||
|
disable = jsonWorkOrder.get("disable", False)
|
||||||
|
self.scriptsWorkOrders.append(LuaPluginWorkOrder(self, file, mnemonic, disable))
|
||||||
|
self.configurationWorkOrders = []
|
||||||
|
for jsonWorkOrder in jsonData.get("configurationWorkOrders"):
|
||||||
|
file = jsonWorkOrder.get("file")
|
||||||
|
mnemonic = jsonWorkOrder.get("mnemonic")
|
||||||
|
disable = jsonWorkOrder.get("disable", False)
|
||||||
|
self.configurationWorkOrders.append(LuaPluginWorkOrder(self, file, mnemonic, disable))
|
||||||
|
|
||||||
|
def setupUI(self, settingsWindow, row:int):
|
||||||
|
# set the game settings
|
||||||
|
self.setSettings(settingsWindow.game.settings)
|
||||||
|
|
||||||
|
if not self.skipUI:
|
||||||
|
# create the plugin choice checkbox interface
|
||||||
|
self.uiWidget: QCheckBox = QCheckBox()
|
||||||
|
self.uiWidget.setChecked(self.isEnabled())
|
||||||
|
self.uiWidget.toggled.connect(lambda: self.applySetting(settingsWindow))
|
||||||
|
|
||||||
|
settingsWindow.pluginsGroupLayout.addWidget(QLabel(self.nameInUI), row, 0)
|
||||||
|
settingsWindow.pluginsGroupLayout.addWidget(self.uiWidget, row, 1, Qt.AlignRight)
|
||||||
|
|
||||||
|
# if needed, create the plugin options special page
|
||||||
|
if settingsWindow.pluginsOptionsPageLayout and self.specificOptions != None:
|
||||||
|
self.optionsGroup: QGroupBox = QGroupBox(self.nameInUI)
|
||||||
|
optionsGroupLayout = QGridLayout();
|
||||||
|
optionsGroupLayout.setAlignment(Qt.AlignTop)
|
||||||
|
self.optionsGroup.setLayout(optionsGroupLayout)
|
||||||
|
settingsWindow.pluginsOptionsPageLayout.addWidget(self.optionsGroup)
|
||||||
|
|
||||||
|
# browse each option in the specific options list
|
||||||
|
row = 0
|
||||||
|
for specificOption in self.specificOptions:
|
||||||
|
nameInSettings = self.nameInSettings + "." + specificOption.mnemonic
|
||||||
|
if not nameInSettings in self.settings.plugins:
|
||||||
|
self.settings.plugins[nameInSettings] = specificOption.defaultValue
|
||||||
|
|
||||||
|
specificOption.uiWidget = QCheckBox()
|
||||||
|
specificOption.uiWidget.setChecked(self.settings.plugins[nameInSettings])
|
||||||
|
#specificOption.uiWidget.setEnabled(False)
|
||||||
|
specificOption.uiWidget.toggled.connect(lambda: self.applySetting(settingsWindow))
|
||||||
|
|
||||||
|
optionsGroupLayout.addWidget(QLabel(specificOption.nameInUI), row, 0)
|
||||||
|
optionsGroupLayout.addWidget(specificOption.uiWidget, row, 1, Qt.AlignRight)
|
||||||
|
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
# disable or enable the UI in the plugins special page
|
||||||
|
self.enableOptionsGroup()
|
||||||
|
|
||||||
|
def enableOptionsGroup(self):
|
||||||
|
if self.optionsGroup:
|
||||||
|
self.optionsGroup.setEnabled(self.isEnabled())
|
||||||
|
|
||||||
|
def setSettings(self, settings):
|
||||||
|
self.settings = settings
|
||||||
|
|
||||||
|
# ensure the setting exist
|
||||||
|
if not self.nameInSettings in self.settings.plugins:
|
||||||
|
self.settings.plugins[self.nameInSettings] = self.defaultValue
|
||||||
|
|
||||||
|
# do the same for each option in the specific options list
|
||||||
|
for specificOption in self.specificOptions:
|
||||||
|
nameInSettings = self.nameInSettings + "." + specificOption.mnemonic
|
||||||
|
if not nameInSettings in self.settings.plugins:
|
||||||
|
self.settings.plugins[nameInSettings] = specificOption.defaultValue
|
||||||
|
|
||||||
|
def applySetting(self, settingsWindow):
|
||||||
|
# apply the main setting
|
||||||
|
self.settings.plugins[self.nameInSettings] = self.uiWidget.isChecked()
|
||||||
|
self.enabled = self.settings.plugins[self.nameInSettings]
|
||||||
|
|
||||||
|
# do the same for each option in the specific options list
|
||||||
|
for specificOption in self.specificOptions:
|
||||||
|
nameInSettings = self.nameInSettings + "." + specificOption.mnemonic
|
||||||
|
self.settings.plugins[nameInSettings] = specificOption.uiWidget.isChecked()
|
||||||
|
|
||||||
|
# disable or enable the UI in the plugins special page
|
||||||
|
self.enableOptionsGroup()
|
||||||
|
|
||||||
|
def injectScripts(self, operation):
|
||||||
|
# set the game settings
|
||||||
|
self.setSettings(operation.game.settings)
|
||||||
|
|
||||||
|
# execute the work order
|
||||||
|
if self.scriptsWorkOrders != None:
|
||||||
|
for workOrder in self.scriptsWorkOrders:
|
||||||
|
workOrder.work(self.mnemonic, operation)
|
||||||
|
|
||||||
|
# serves for subclasses
|
||||||
|
return self.isEnabled()
|
||||||
|
|
||||||
|
def injectConfiguration(self, operation):
|
||||||
|
# set the game settings
|
||||||
|
self.setSettings(operation.game.settings)
|
||||||
|
|
||||||
|
# inject the plugin options
|
||||||
|
if len(self.specificOptions) > 0:
|
||||||
|
defineAllOptions = ""
|
||||||
|
for specificOption in self.specificOptions:
|
||||||
|
nameInSettings = self.nameInSettings + "." + specificOption.mnemonic
|
||||||
|
value = "true" if self.settings.plugins[nameInSettings] else "false"
|
||||||
|
defineAllOptions += f" dcsLiberation.plugins.{self.mnemonic}.{specificOption.mnemonic} = {value} \n"
|
||||||
|
|
||||||
|
|
||||||
|
lua = f"-- {self.mnemonic} plugin configuration.\n"
|
||||||
|
lua += "\n"
|
||||||
|
lua += "if dcsLiberation then\n"
|
||||||
|
lua += " if not dcsLiberation.plugins then \n"
|
||||||
|
lua += " dcsLiberation.plugins = {}\n"
|
||||||
|
lua += " end\n"
|
||||||
|
lua += f" dcsLiberation.plugins.{self.mnemonic} = {{}}\n"
|
||||||
|
lua += defineAllOptions
|
||||||
|
lua += "end"
|
||||||
|
|
||||||
|
operation.injectLuaTrigger(lua, f"{self.mnemonic} plugin configuration")
|
||||||
|
|
||||||
|
# execute the work order
|
||||||
|
if self.configurationWorkOrders != None:
|
||||||
|
for workOrder in self.configurationWorkOrders:
|
||||||
|
workOrder.work(self.mnemonic, operation)
|
||||||
|
|
||||||
|
# serves for subclasses
|
||||||
|
return self.isEnabled()
|
||||||
|
|
||||||
|
def isEnabled(self) -> bool:
|
||||||
|
if not self.settings:
|
||||||
|
return False
|
||||||
|
|
||||||
|
self.setSettings(self.settings) # create the necessary settings keys if needed
|
||||||
|
|
||||||
|
return self.settings != None and self.settings.plugins[self.nameInSettings]
|
||||||
|
|
||||||
|
def hasUI(self) -> bool:
|
||||||
|
return not self.skipUI
|
||||||
43
plugin/manager.py
Normal file
43
plugin/manager.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
from .luaplugin import LuaPlugin
|
||||||
|
from typing import List
|
||||||
|
import glob
|
||||||
|
from pathlib import Path
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
class LuaPluginManager():
|
||||||
|
PLUGINS_RESOURCE_PATH = Path("resources/plugins")
|
||||||
|
PLUGINS_LIST_FILENAME = "plugins.json"
|
||||||
|
PLUGINS_JSON_FILENAME = "plugin.json"
|
||||||
|
|
||||||
|
__plugins = None
|
||||||
|
def __init__(self):
|
||||||
|
if not LuaPluginManager.__plugins:
|
||||||
|
LuaPluginManager.__plugins= []
|
||||||
|
jsonFile:Path = Path(LuaPluginManager.PLUGINS_RESOURCE_PATH, LuaPluginManager.PLUGINS_LIST_FILENAME)
|
||||||
|
if jsonFile.exists():
|
||||||
|
logging.info(f"Reading plugins list from {jsonFile}")
|
||||||
|
|
||||||
|
jsonData = json.loads(jsonFile.read_text())
|
||||||
|
for plugin in jsonData:
|
||||||
|
jsonPluginFolder = Path(LuaPluginManager.PLUGINS_RESOURCE_PATH, plugin)
|
||||||
|
jsonPluginFile = Path(jsonPluginFolder, LuaPluginManager.PLUGINS_JSON_FILENAME)
|
||||||
|
if jsonPluginFile.exists():
|
||||||
|
logging.info(f"Reading plugin {plugin} from {jsonPluginFile}")
|
||||||
|
plugin = LuaPlugin(jsonPluginFile)
|
||||||
|
LuaPluginManager.__plugins.append(plugin)
|
||||||
|
else:
|
||||||
|
logging.error(f"Missing configuration file {jsonPluginFile} for plugin {plugin}")
|
||||||
|
else:
|
||||||
|
logging.error(f"Missing plugins list file {jsonFile}")
|
||||||
|
|
||||||
|
def getPlugins(self):
|
||||||
|
return LuaPluginManager.__plugins
|
||||||
|
|
||||||
|
def getPlugin(self, pluginName):
|
||||||
|
for plugin in LuaPluginManager.__plugins:
|
||||||
|
if plugin.mnemonic == pluginName:
|
||||||
|
return plugin
|
||||||
|
|
||||||
|
return None
|
||||||
@ -112,6 +112,8 @@ def load_icons():
|
|||||||
ICONS["Generator"] = QPixmap("./resources/ui/misc/"+get_theme_icons()+"/generator.png")
|
ICONS["Generator"] = QPixmap("./resources/ui/misc/"+get_theme_icons()+"/generator.png")
|
||||||
ICONS["Missile"] = QPixmap("./resources/ui/misc/"+get_theme_icons()+"/missile.png")
|
ICONS["Missile"] = QPixmap("./resources/ui/misc/"+get_theme_icons()+"/missile.png")
|
||||||
ICONS["Cheat"] = QPixmap("./resources/ui/misc/"+get_theme_icons()+"/cheat.png")
|
ICONS["Cheat"] = QPixmap("./resources/ui/misc/"+get_theme_icons()+"/cheat.png")
|
||||||
|
ICONS["Plugins"] = QPixmap("./resources/ui/misc/"+get_theme_icons()+"/plugins.png")
|
||||||
|
ICONS["PluginsOptions"] = QPixmap("./resources/ui/misc/"+get_theme_icons()+"/pluginsoptions.png")
|
||||||
|
|
||||||
ICONS["TaskCAS"] = QPixmap("./resources/ui/tasks/cas.png")
|
ICONS["TaskCAS"] = QPixmap("./resources/ui/tasks/cas.png")
|
||||||
ICONS["TaskCAP"] = QPixmap("./resources/ui/tasks/cap.png")
|
ICONS["TaskCAP"] = QPixmap("./resources/ui/tasks/cap.png")
|
||||||
|
|||||||
@ -26,7 +26,7 @@ from game.infos.information import Information
|
|||||||
from qt_ui.widgets.QLabeledWidget import QLabeledWidget
|
from qt_ui.widgets.QLabeledWidget import QLabeledWidget
|
||||||
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
|
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
|
||||||
from qt_ui.windows.finances.QFinancesMenu import QHorizontalSeparationLine
|
from qt_ui.windows.finances.QFinancesMenu import QHorizontalSeparationLine
|
||||||
|
from plugin import LuaPluginManager
|
||||||
|
|
||||||
class CheatSettingsBox(QGroupBox):
|
class CheatSettingsBox(QGroupBox):
|
||||||
def __init__(self, game: Game, apply_settings: Callable[[], None]) -> None:
|
def __init__(self, game: Game, apply_settings: Callable[[], None]) -> None:
|
||||||
@ -51,6 +51,8 @@ class QSettingsWindow(QDialog):
|
|||||||
super(QSettingsWindow, self).__init__()
|
super(QSettingsWindow, self).__init__()
|
||||||
|
|
||||||
self.game = game
|
self.game = game
|
||||||
|
self.pluginsPage = None
|
||||||
|
self.pluginsOptionsPage = None
|
||||||
|
|
||||||
self.setModal(True)
|
self.setModal(True)
|
||||||
self.setWindowTitle("Settings")
|
self.setWindowTitle("Settings")
|
||||||
@ -69,38 +71,53 @@ class QSettingsWindow(QDialog):
|
|||||||
|
|
||||||
self.categoryModel = QStandardItemModel(self.categoryList)
|
self.categoryModel = QStandardItemModel(self.categoryList)
|
||||||
|
|
||||||
|
self.categoryList.setIconSize(QSize(32, 32))
|
||||||
|
|
||||||
|
self.initDifficultyLayout()
|
||||||
difficulty = QStandardItem("Difficulty")
|
difficulty = QStandardItem("Difficulty")
|
||||||
difficulty.setIcon(CONST.ICONS["Missile"])
|
difficulty.setIcon(CONST.ICONS["Missile"])
|
||||||
difficulty.setEditable(False)
|
difficulty.setEditable(False)
|
||||||
difficulty.setSelectable(True)
|
difficulty.setSelectable(True)
|
||||||
|
self.categoryModel.appendRow(difficulty)
|
||||||
|
self.right_layout.addWidget(self.difficultyPage)
|
||||||
|
|
||||||
|
self.initGeneratorLayout()
|
||||||
generator = QStandardItem("Mission Generator")
|
generator = QStandardItem("Mission Generator")
|
||||||
generator.setIcon(CONST.ICONS["Generator"])
|
generator.setIcon(CONST.ICONS["Generator"])
|
||||||
generator.setEditable(False)
|
generator.setEditable(False)
|
||||||
generator.setSelectable(True)
|
generator.setSelectable(True)
|
||||||
|
self.categoryModel.appendRow(generator)
|
||||||
|
self.right_layout.addWidget(self.generatorPage)
|
||||||
|
|
||||||
|
self.initCheatLayout()
|
||||||
cheat = QStandardItem("Cheat Menu")
|
cheat = QStandardItem("Cheat Menu")
|
||||||
cheat.setIcon(CONST.ICONS["Cheat"])
|
cheat.setIcon(CONST.ICONS["Cheat"])
|
||||||
cheat.setEditable(False)
|
cheat.setEditable(False)
|
||||||
cheat.setSelectable(True)
|
cheat.setSelectable(True)
|
||||||
|
|
||||||
self.categoryList.setIconSize(QSize(32, 32))
|
|
||||||
self.categoryModel.appendRow(difficulty)
|
|
||||||
self.categoryModel.appendRow(generator)
|
|
||||||
self.categoryModel.appendRow(cheat)
|
self.categoryModel.appendRow(cheat)
|
||||||
|
self.right_layout.addWidget(self.cheatPage)
|
||||||
|
|
||||||
|
self.initPluginsLayout()
|
||||||
|
if self.pluginsPage:
|
||||||
|
plugins = QStandardItem("LUA Plugins")
|
||||||
|
plugins.setIcon(CONST.ICONS["Plugins"])
|
||||||
|
plugins.setEditable(False)
|
||||||
|
plugins.setSelectable(True)
|
||||||
|
self.categoryModel.appendRow(plugins)
|
||||||
|
self.right_layout.addWidget(self.pluginsPage)
|
||||||
|
if self.pluginsOptionsPage:
|
||||||
|
pluginsOptions = QStandardItem("LUA Plugins Options")
|
||||||
|
pluginsOptions.setIcon(CONST.ICONS["PluginsOptions"])
|
||||||
|
pluginsOptions.setEditable(False)
|
||||||
|
pluginsOptions.setSelectable(True)
|
||||||
|
self.categoryModel.appendRow(pluginsOptions)
|
||||||
|
self.right_layout.addWidget(self.pluginsOptionsPage)
|
||||||
|
|
||||||
self.categoryList.setSelectionBehavior(QAbstractItemView.SelectRows)
|
self.categoryList.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||||
self.categoryList.setModel(self.categoryModel)
|
self.categoryList.setModel(self.categoryModel)
|
||||||
self.categoryList.selectionModel().setCurrentIndex(self.categoryList.indexAt(QPoint(1,1)), QItemSelectionModel.Select)
|
self.categoryList.selectionModel().setCurrentIndex(self.categoryList.indexAt(QPoint(1,1)), QItemSelectionModel.Select)
|
||||||
self.categoryList.selectionModel().selectionChanged.connect(self.onSelectionChanged)
|
self.categoryList.selectionModel().selectionChanged.connect(self.onSelectionChanged)
|
||||||
|
|
||||||
self.initDifficultyLayout()
|
|
||||||
self.initGeneratorLayout()
|
|
||||||
self.initCheatLayout()
|
|
||||||
|
|
||||||
self.right_layout.addWidget(self.difficultyPage)
|
|
||||||
self.right_layout.addWidget(self.generatorPage)
|
|
||||||
self.right_layout.addWidget(self.cheatPage)
|
|
||||||
|
|
||||||
self.layout.addWidget(self.categoryList, 0, 0, 1, 1)
|
self.layout.addWidget(self.categoryList, 0, 0, 1, 1)
|
||||||
self.layout.addLayout(self.right_layout, 0, 1, 5, 1)
|
self.layout.addLayout(self.right_layout, 0, 1, 5, 1)
|
||||||
@ -200,28 +217,10 @@ class QSettingsWindow(QDialog):
|
|||||||
self.generate_marks.setChecked(self.game.settings.generate_marks)
|
self.generate_marks.setChecked(self.game.settings.generate_marks)
|
||||||
self.generate_marks.toggled.connect(self.applySettings)
|
self.generate_marks.toggled.connect(self.applySettings)
|
||||||
|
|
||||||
|
|
||||||
if not hasattr(self.game.settings, "include_jtac_if_available"):
|
|
||||||
self.game.settings.include_jtac_if_available = True
|
|
||||||
if not hasattr(self.game.settings, "jtac_smoke_on"):
|
|
||||||
self.game.settings.jtac_smoke_on= True
|
|
||||||
|
|
||||||
self.include_jtac_if_available = QCheckBox()
|
|
||||||
self.include_jtac_if_available.setChecked(self.game.settings.include_jtac_if_available)
|
|
||||||
self.include_jtac_if_available.toggled.connect(self.applySettings)
|
|
||||||
|
|
||||||
self.jtac_smoke_on = QCheckBox()
|
|
||||||
self.jtac_smoke_on.setChecked(self.game.settings.jtac_smoke_on)
|
|
||||||
self.jtac_smoke_on.toggled.connect(self.applySettings)
|
|
||||||
|
|
||||||
self.gameplayLayout.addWidget(QLabel("Use Supercarrier Module"), 0, 0)
|
self.gameplayLayout.addWidget(QLabel("Use Supercarrier Module"), 0, 0)
|
||||||
self.gameplayLayout.addWidget(self.supercarrier, 0, 1, Qt.AlignRight)
|
self.gameplayLayout.addWidget(self.supercarrier, 0, 1, Qt.AlignRight)
|
||||||
self.gameplayLayout.addWidget(QLabel("Put Objective Markers on Map"), 1, 0)
|
self.gameplayLayout.addWidget(QLabel("Put Objective Markers on Map"), 1, 0)
|
||||||
self.gameplayLayout.addWidget(self.generate_marks, 1, 1, Qt.AlignRight)
|
self.gameplayLayout.addWidget(self.generate_marks, 1, 1, Qt.AlignRight)
|
||||||
self.gameplayLayout.addWidget(QLabel("Include JTAC (If available)"), 2, 0)
|
|
||||||
self.gameplayLayout.addWidget(self.include_jtac_if_available, 2, 1, Qt.AlignRight)
|
|
||||||
self.gameplayLayout.addWidget(QLabel("Enable JTAC smoke markers"), 3, 0)
|
|
||||||
self.gameplayLayout.addWidget(self.jtac_smoke_on, 3, 1, Qt.AlignRight)
|
|
||||||
|
|
||||||
self.performance = QGroupBox("Performance")
|
self.performance = QGroupBox("Performance")
|
||||||
self.performanceLayout = QGridLayout()
|
self.performanceLayout = QGridLayout()
|
||||||
@ -318,6 +317,34 @@ class QSettingsWindow(QDialog):
|
|||||||
self.moneyCheatBoxLayout.addWidget(btn, i/2, i%2)
|
self.moneyCheatBoxLayout.addWidget(btn, i/2, i%2)
|
||||||
self.cheatLayout.addWidget(self.moneyCheatBox, stretch=1)
|
self.cheatLayout.addWidget(self.moneyCheatBox, stretch=1)
|
||||||
|
|
||||||
|
def initPluginsLayout(self):
|
||||||
|
uiPrepared = False
|
||||||
|
row:int = 0
|
||||||
|
for plugin in LuaPluginManager().getPlugins():
|
||||||
|
if plugin.hasUI():
|
||||||
|
if not uiPrepared:
|
||||||
|
uiPrepared = True
|
||||||
|
|
||||||
|
self.pluginsOptionsPage = QWidget()
|
||||||
|
self.pluginsOptionsPageLayout = QVBoxLayout()
|
||||||
|
self.pluginsOptionsPageLayout.setAlignment(Qt.AlignTop)
|
||||||
|
self.pluginsOptionsPage.setLayout(self.pluginsOptionsPageLayout)
|
||||||
|
|
||||||
|
self.pluginsPage = QWidget()
|
||||||
|
self.pluginsPageLayout = QVBoxLayout()
|
||||||
|
self.pluginsPageLayout.setAlignment(Qt.AlignTop)
|
||||||
|
self.pluginsPage.setLayout(self.pluginsPageLayout)
|
||||||
|
|
||||||
|
self.pluginsGroup = QGroupBox("Plugins")
|
||||||
|
self.pluginsGroupLayout = QGridLayout();
|
||||||
|
self.pluginsGroupLayout.setAlignment(Qt.AlignTop)
|
||||||
|
self.pluginsGroup.setLayout(self.pluginsGroupLayout)
|
||||||
|
|
||||||
|
self.pluginsPageLayout.addWidget(self.pluginsGroup)
|
||||||
|
|
||||||
|
plugin.setupUI(self, row)
|
||||||
|
row = row + 1
|
||||||
|
|
||||||
def cheatLambda(self, amount):
|
def cheatLambda(self, amount):
|
||||||
return lambda: self.cheatMoney(amount)
|
return lambda: self.cheatMoney(amount)
|
||||||
|
|
||||||
@ -339,8 +366,6 @@ class QSettingsWindow(QDialog):
|
|||||||
self.game.settings.map_coalition_visibility = self.mapVisibiitySelection.currentData()
|
self.game.settings.map_coalition_visibility = self.mapVisibiitySelection.currentData()
|
||||||
self.game.settings.external_views_allowed = self.ext_views.isChecked()
|
self.game.settings.external_views_allowed = self.ext_views.isChecked()
|
||||||
self.game.settings.generate_marks = self.generate_marks.isChecked()
|
self.game.settings.generate_marks = self.generate_marks.isChecked()
|
||||||
self.game.settings.include_jtac_if_available = self.include_jtac_if_available.isChecked()
|
|
||||||
self.game.settings.jtac_smoke_on = self.jtac_smoke_on.isChecked()
|
|
||||||
|
|
||||||
self.game.settings.supercarrier = self.supercarrier.isChecked()
|
self.game.settings.supercarrier = self.supercarrier.isChecked()
|
||||||
|
|
||||||
|
|||||||
BIN
resources/plugins/_doc/0.png
Normal file
BIN
resources/plugins/_doc/0.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
BIN
resources/plugins/_doc/1.png
Normal file
BIN
resources/plugins/_doc/1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
BIN
resources/plugins/_doc/2.png
Normal file
BIN
resources/plugins/_doc/2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
84
resources/plugins/_doc/plugins_readme.md
Normal file
84
resources/plugins/_doc/plugins_readme.md
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
# LUA Plugin system
|
||||||
|
|
||||||
|
This plugin system was made for injecting LUA scripts in dcs-liberation missions.
|
||||||
|
|
||||||
|
The resources for the plugins are stored in the `resources/plugins` folder ; each plugin has its own folder.
|
||||||
|
|
||||||
|
## How does the system work ?
|
||||||
|
|
||||||
|
The application first reads the `resources/plugins/plugins.json` file to get a list of plugins to load, in order.
|
||||||
|
Each entry in this list should correspond to a subfolder of the `resources/plugins` directory, where a `plugin.json` file exists.
|
||||||
|
This file is the description of the plugin.
|
||||||
|
|
||||||
|
### plugin.json
|
||||||
|
|
||||||
|
The *base* and *jtacautolase* plugins both are included in the standard dcs-liberation distribution.
|
||||||
|
You can check their respective `plugin.json` files to understand how they work.
|
||||||
|
Here's a quick rundown of the file's components :
|
||||||
|
|
||||||
|
- `mnemonic` : the short, technical name of the plugin. It's the name of the folder, and the name of the plugin in the application's settings
|
||||||
|
- `skipUI` : if *true*, this plugin will not appear in the plugins selection user interface. Useful to force a plugin ON or OFF (see the *base* plugin)
|
||||||
|
- `nameInUI` : the title of the plugin as it will appear in the plugins selection user interface.
|
||||||
|
- `defaultValue` : the selection value of the plugin, when first installed ; if true, plugin is selected.
|
||||||
|
- `specificOptions` : a list of specific plugin options
|
||||||
|
- `nameInUI` : the title of the option as it will appear in the plugins specific options user interface.
|
||||||
|
- `mnemonic` : the short, technical name of the option. It's the name of the LUA variable passed to the configuration script, and the name of the option in the application's settings
|
||||||
|
- `defaultValue` : the selection value of the option, when first installed ; if true, option is selected.
|
||||||
|
- `scriptsWorkOrders` : a list of work orders that can be used to load or disable loading a specific LUA script
|
||||||
|
- `file` : the name of the LUA file in the plugin folder.
|
||||||
|
- `mnemonic` : the technical name of the LUA component. The filename may be more precise than needed (e.g. include a version number) ; this is used to load each file only once, and also to disable loading a file
|
||||||
|
- `disable` : if true, the script will be disabled instead of loaded
|
||||||
|
- `configurationWorkOrders` : a list of work orders that can be used to load a configuration LUA script (same description as above)
|
||||||
|
|
||||||
|
## Standard plugins
|
||||||
|
|
||||||
|
### The *base* plugin
|
||||||
|
|
||||||
|
The *base* plugin contains the scripts that are going to be injected in every dcs-liberation missions.
|
||||||
|
It is mandatory.
|
||||||
|
|
||||||
|
### The *JTACAutolase* plugin
|
||||||
|
|
||||||
|
This plugin replaces the vanilla JTAC functionality in dcs-liberation.
|
||||||
|
|
||||||
|
### The *VEAF framework* plugin
|
||||||
|
|
||||||
|
When enabled, this plugin will inject and configure the VEAF Framework scripts in the mission.
|
||||||
|
|
||||||
|
These scripts add a lot of runtime functionalities :
|
||||||
|
|
||||||
|
- spawning of units and groups (and portable TACANs)
|
||||||
|
- air-to-ground missions
|
||||||
|
- air-to-air missions
|
||||||
|
- transport missions
|
||||||
|
- carrier operations (not Moose)
|
||||||
|
- tanker move
|
||||||
|
- weather and ATC
|
||||||
|
- shelling a zone, lighting it up
|
||||||
|
- managing assets (tankers, awacs, aircraft carriers) : getting info, state, respawning them if needed
|
||||||
|
- managing named points (position, info, ATC)
|
||||||
|
- managing a dynamic radio menu
|
||||||
|
- managing remote calls to the mission through NIOD (RPC) and SLMOD (LUA sockets)
|
||||||
|
- managing security (not allowing everyone to do every action)
|
||||||
|
- define groups templates
|
||||||
|
|
||||||
|
You can find the *VEAF Framework* plugin [on GitHub](https://github.com/VEAF/dcs-liberation-veaf-framework/releases)
|
||||||
|
For more information, please visit the [VEAF Framework documentation site](https://veaf.github.io/VEAF-Mission-Creation-Tools/) (work in progress)
|
||||||
|
|
||||||
|
## Custom plugins
|
||||||
|
|
||||||
|
The easiest way to create a custom plugin is to copy an existing plugin, and modify the files.
|
||||||
|
|
||||||
|
## New settings pages
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Custom plugins can be enabled or disabled in the new *LUA Plugins* settings page.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
For plugins which expose specific options (such as "use smoke" for the *JTACAutoLase* plugin), the *LUA Plugins Options* settings page lists these options.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
22
resources/plugins/base/plugin.json
Normal file
22
resources/plugins/base/plugin.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"mnemonic": "base",
|
||||||
|
"skipUI": true,
|
||||||
|
"nameInUI": "",
|
||||||
|
"defaultValue": true,
|
||||||
|
"specificOptions": [],
|
||||||
|
"scriptsWorkOrders": [
|
||||||
|
{
|
||||||
|
"file": "mist_4_3_74.lua",
|
||||||
|
"mnemonic": "mist"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "json.lua",
|
||||||
|
"mnemonic": "json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "dcs_liberation.lua",
|
||||||
|
"mnemonic": "liberation"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"configurationWorkOrders": []
|
||||||
|
}
|
||||||
37
resources/plugins/jtacautolase/jtacautolase-config.lua
Normal file
37
resources/plugins/jtacautolase/jtacautolase-config.lua
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
-- configuration file for the JTAC Autolase framework
|
||||||
|
--
|
||||||
|
-- This configuration is tailored for a mission generated by DCS Liberation
|
||||||
|
-- see https://github.com/Khopa/dcs_liberation
|
||||||
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- JTACAutolase plugin - configuration
|
||||||
|
env.info("DCSLiberation|JTACAutolase plugin - configuration")
|
||||||
|
|
||||||
|
if dcsLiberation then
|
||||||
|
env.info(string.format("DCSLiberation|JTACAutolase plugin - dcsLiberation"))
|
||||||
|
|
||||||
|
-- specific options
|
||||||
|
local smoke = false
|
||||||
|
|
||||||
|
-- retrieve specific options values
|
||||||
|
if dcsLiberation.plugins then
|
||||||
|
env.info(string.format("DCSLiberation|JTACAutolase plugin - dcsLiberation.plugins"))
|
||||||
|
|
||||||
|
if dcsLiberation.plugins.jtacautolase then
|
||||||
|
env.info(string.format("DCSLiberation|JTACAutolase plugin - dcsLiberation.plugins.jtacautolase"))
|
||||||
|
smoke = dcsLiberation.plugins.jtacautolase.smoke
|
||||||
|
env.info(string.format("DCSLiberation|JTACAutolase plugin - smoke = %s",tostring(smoke)))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- actual configuration code
|
||||||
|
for _, jtac in pairs(dcsLiberation.JTACs) do
|
||||||
|
env.info(string.format("DCSLiberation|JTACAutolase plugin - setting up %s",jtac.dcsUnit))
|
||||||
|
if JTACAutoLase then
|
||||||
|
env.info(string.format("DCSLiberation|JTACAutolase plugin - calling dcsLiberation.JTACAutoLase"))
|
||||||
|
JTACAutoLase(jtac.dcsUnit, jtac.laserCode, smoke, 'vehicle')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
6822
resources/plugins/jtacautolase/mist_4_3_74.lua
Normal file
6822
resources/plugins/jtacautolase/mist_4_3_74.lua
Normal file
File diff suppressed because it is too large
Load Diff
28
resources/plugins/jtacautolase/plugin.json
Normal file
28
resources/plugins/jtacautolase/plugin.json
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"mnemonic": "jtacautolase",
|
||||||
|
"nameInUI": "JTAC Autolase",
|
||||||
|
"defaultValue": true,
|
||||||
|
"specificOptions": [
|
||||||
|
{
|
||||||
|
"nameInUI": "Use smoke",
|
||||||
|
"mnemonic": "smoke",
|
||||||
|
"defaultValue": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"scriptsWorkOrders": [
|
||||||
|
{
|
||||||
|
"file": "mist_4_3_74.lua",
|
||||||
|
"mnemonic": "mist"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "JTACAutoLase.lua",
|
||||||
|
"mnemonic": "jtacautolase-script"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"configurationWorkOrders": [
|
||||||
|
{
|
||||||
|
"file": "jtacautolase-config.lua",
|
||||||
|
"mnemonic": "jtacautolase-config"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
5
resources/plugins/plugins.json
Normal file
5
resources/plugins/plugins.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
[
|
||||||
|
"veaf",
|
||||||
|
"jtacautolase",
|
||||||
|
"base"
|
||||||
|
]
|
||||||
@ -1,29 +0,0 @@
|
|||||||
# this is a list of lua scripts that will be injected in the mission, in the same order
|
|
||||||
mist.lua
|
|
||||||
Moose.lua
|
|
||||||
CTLD.lua
|
|
||||||
NIOD.lua
|
|
||||||
WeatherMark.lua
|
|
||||||
veaf.lua
|
|
||||||
dcsUnits.lua
|
|
||||||
# JTACAutoLase is an empty file, only there to disable loading the official script (already included in CTLD)
|
|
||||||
JTACAutoLase.lua
|
|
||||||
veafAssets.lua
|
|
||||||
veafCarrierOperations.lua
|
|
||||||
veafCarrierOperations2.lua
|
|
||||||
veafCasMission.lua
|
|
||||||
veafCombatMission.lua
|
|
||||||
veafCombatZone.lua
|
|
||||||
veafGrass.lua
|
|
||||||
veafInterpreter.lua
|
|
||||||
veafMarkers.lua
|
|
||||||
veafMove.lua
|
|
||||||
veafNamedPoints.lua
|
|
||||||
veafRadio.lua
|
|
||||||
veafRemote.lua
|
|
||||||
veafSecurity.lua
|
|
||||||
veafShortcuts.lua
|
|
||||||
veafSpawn.lua
|
|
||||||
veafTransportMission.lua
|
|
||||||
veafUnits.lua
|
|
||||||
missionConfig.lua
|
|
||||||
BIN
resources/ui/misc/light/plugins.png
Normal file
BIN
resources/ui/misc/light/plugins.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
BIN
resources/ui/misc/light/pluginsoptions.png
Normal file
BIN
resources/ui/misc/light/pluginsoptions.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
Loading…
x
Reference in New Issue
Block a user