mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2026-08-23 11:40:57 +00:00
Merge branch 'develop' into FF/Ops
This commit is contained in:
@@ -40,6 +40,7 @@ do
|
|||||||
-- @field #string ClassName Class name.
|
-- @field #string ClassName Class name.
|
||||||
-- @field #number ID ID of this cargo.
|
-- @field #number ID ID of this cargo.
|
||||||
-- @field #string Name Name for menu.
|
-- @field #string Name Name for menu.
|
||||||
|
-- @field #string DisplayName Display name for menu/messages.
|
||||||
-- @field #table Templates Table of #POSITIONABLE objects.
|
-- @field #table Templates Table of #POSITIONABLE objects.
|
||||||
-- @field #string CargoType Enumerator of Type.
|
-- @field #string CargoType Enumerator of Type.
|
||||||
-- @field #boolean HasBeenMoved Flag for moving.
|
-- @field #boolean HasBeenMoved Flag for moving.
|
||||||
@@ -66,6 +67,7 @@ CTLD_CARGO = {
|
|||||||
ClassName = "CTLD_CARGO",
|
ClassName = "CTLD_CARGO",
|
||||||
ID = 0,
|
ID = 0,
|
||||||
Name = "none",
|
Name = "none",
|
||||||
|
DisplayName = "none",
|
||||||
Templates = {},
|
Templates = {},
|
||||||
CargoType = "none",
|
CargoType = "none",
|
||||||
HasBeenMoved = false,
|
HasBeenMoved = false,
|
||||||
@@ -125,6 +127,7 @@ CTLD_CARGO = {
|
|||||||
self:T({ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped})
|
self:T({ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped})
|
||||||
self.ID = ID or math.random(100000,1000000)
|
self.ID = ID or math.random(100000,1000000)
|
||||||
self.Name = Name or "none" -- #string
|
self.Name = Name or "none" -- #string
|
||||||
|
self.DisplayName = Name or "none" -- #string
|
||||||
self.Templates = Templates or {} -- #table
|
self.Templates = Templates or {} -- #table
|
||||||
self.CargoType = Sorte or "type" -- #CTLD_CARGO.Enum
|
self.CargoType = Sorte or "type" -- #CTLD_CARGO.Enum
|
||||||
self.HasBeenMoved = HasBeenMoved or false -- #boolean
|
self.HasBeenMoved = HasBeenMoved or false -- #boolean
|
||||||
@@ -254,6 +257,26 @@ CTLD_CARGO = {
|
|||||||
return self.Name
|
return self.Name
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Set display name.
|
||||||
|
-- @param #CTLD_CARGO self
|
||||||
|
-- @param #string DisplayName Display label used in menus/messages (optional).
|
||||||
|
-- @return #CTLD_CARGO self
|
||||||
|
function CTLD_CARGO:SetDisplayName(DisplayName)
|
||||||
|
if type(DisplayName) == "string" and DisplayName ~= "" then
|
||||||
|
self.DisplayName = DisplayName
|
||||||
|
else
|
||||||
|
self.DisplayName = self.Name
|
||||||
|
end
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Query display name.
|
||||||
|
-- @param #CTLD_CARGO self
|
||||||
|
-- @return #string Display name, or Name if not set
|
||||||
|
function CTLD_CARGO:GetDisplayName()
|
||||||
|
return self.DisplayName or self.Name
|
||||||
|
end
|
||||||
|
|
||||||
--- Query Templates.
|
--- Query Templates.
|
||||||
-- @param #CTLD_CARGO self
|
-- @param #CTLD_CARGO self
|
||||||
-- @return #table Templates
|
-- @return #table Templates
|
||||||
@@ -1491,7 +1514,7 @@ CTLD.UnitTypeCapabilities = {
|
|||||||
["C-130J-30"] = {type="C-130J-30", crates=true, troops=true, cratelimit = 7, trooplimit = 64, length = 35, cargoweightlimit = 21500}, -- 19t cargo, 64 paratroopers.
|
["C-130J-30"] = {type="C-130J-30", crates=true, troops=true, cratelimit = 7, trooplimit = 64, length = 35, cargoweightlimit = 21500}, -- 19t cargo, 64 paratroopers.
|
||||||
--Actually it's longer, but the center coord is off-center of the model.
|
--Actually it's longer, but the center coord is off-center of the model.
|
||||||
["UH-60L"] = {type="UH-60L", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats
|
["UH-60L"] = {type="UH-60L", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats
|
||||||
["UH-60L_DAP"] = {type="UH-60L_DAP", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 16, cargoweightlimit = 500}, -- UH-60L DAP is an attack helo but can do limited CSAR and CTLD
|
["UH-60L_DAP"] = {type="UH-60L_DAP", crates=false, troops=true, cratelimit = 2, trooplimit = 2, length = 16, cargoweightlimit = 3000}, -- UH-60L DAP is an attack helo but can do limited CSAR and CTLD
|
||||||
["MH-60R"] = {type="MH-60R", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats
|
["MH-60R"] = {type="MH-60R", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats
|
||||||
["SH-60B"] = {type="SH-60B", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats
|
["SH-60B"] = {type="SH-60B", crates=true, troops=true, cratelimit = 2, trooplimit = 20, length = 16, cargoweightlimit = 3500}, -- 4t cargo, 20 (unsec) seats
|
||||||
["AH-64D_BLK_II"] = {type="AH-64D_BLK_II", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 17, cargoweightlimit = 200}, -- 2 ppl **outside** the helo
|
["AH-64D_BLK_II"] = {type="AH-64D_BLK_II", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 17, cargoweightlimit = 200}, -- 2 ppl **outside** the helo
|
||||||
@@ -1624,6 +1647,9 @@ function CTLD:New(Coalition, Prefixes, Alias)
|
|||||||
self.Cargo_Crates = {}
|
self.Cargo_Crates = {}
|
||||||
self.Cargo_Troops = {}
|
self.Cargo_Troops = {}
|
||||||
self.Cargo_Statics = {}
|
self.Cargo_Statics = {}
|
||||||
|
self._troopsByName = {}
|
||||||
|
self._crateOrStaticByName = {}
|
||||||
|
self._cargoByTemplate = {}
|
||||||
self.Loaded_Cargo = {}
|
self.Loaded_Cargo = {}
|
||||||
self.Spawned_Crates = {}
|
self.Spawned_Crates = {}
|
||||||
self.Spawned_Cargo = {}
|
self.Spawned_Cargo = {}
|
||||||
@@ -2137,6 +2163,35 @@ function CTLD:_GetUnitCapabilities(Unit)
|
|||||||
return capabilities
|
return capabilities
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- (Internal) Resolve cargo label for menus/messages.
|
||||||
|
-- @param #CTLD self
|
||||||
|
-- @param #CTLD_CARGO Cargo Cargo object or cargo name.
|
||||||
|
-- @return #string Cargo label
|
||||||
|
function CTLD:_GetCargoDisplayName(Cargo)
|
||||||
|
if type(Cargo) == "table" then
|
||||||
|
if Cargo.GetDisplayName then
|
||||||
|
local dname = Cargo:GetDisplayName()
|
||||||
|
if type(dname) == "string" and dname ~= "" then
|
||||||
|
return dname
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if Cargo.GetName then
|
||||||
|
local name = Cargo:GetName()
|
||||||
|
if type(name) == "string" and name ~= "" then
|
||||||
|
return name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if type(Cargo.Name) == "string" and Cargo.Name ~= "" then
|
||||||
|
return Cargo.Name
|
||||||
|
end
|
||||||
|
return "Unknown"
|
||||||
|
end
|
||||||
|
if type(Cargo) == "string" and Cargo ~= "" then
|
||||||
|
return Cargo
|
||||||
|
end
|
||||||
|
return "Unknown"
|
||||||
|
end
|
||||||
|
|
||||||
--- (User) Function to allow transport via Combined Arms Trucks.
|
--- (User) Function to allow transport via Combined Arms Trucks.
|
||||||
-- @param #CTLD self
|
-- @param #CTLD self
|
||||||
-- @param #boolean OnOff Switch on (true) or off (false).
|
-- @param #boolean OnOff Switch on (true) or off (false).
|
||||||
@@ -2367,9 +2422,9 @@ end
|
|||||||
|
|
||||||
--- (Internal) Function to check if a unit is a C-130J
|
--- (Internal) Function to check if a unit is a C-130J
|
||||||
-- @param #CTLD self
|
-- @param #CTLD self
|
||||||
function CTLD:IsC130J(Unit)
|
function CTLD:IsC130J(Unit, IgnoreUseC130Flag)
|
||||||
if not Unit then return false end
|
if not Unit then return false end
|
||||||
if not self.UseC130LoadAndUnload then return false end
|
if not IgnoreUseC130Flag and not self.UseC130LoadAndUnload then return false end
|
||||||
self.C130JUnits = self.C130JUnits or {}
|
self.C130JUnits = self.C130JUnits or {}
|
||||||
local unitname = Unit:GetName() or "none"
|
local unitname = Unit:GetName() or "none"
|
||||||
return self.C130JUnits[unitname] == true
|
return self.C130JUnits[unitname] == true
|
||||||
@@ -2395,10 +2450,16 @@ end
|
|||||||
-- @return #CTLD_CARGO Cargo object, nil if it cannot be found
|
-- @return #CTLD_CARGO Cargo object, nil if it cannot be found
|
||||||
function CTLD:_FindTroopsCargoObject(Name)
|
function CTLD:_FindTroopsCargoObject(Name)
|
||||||
self:T(self.lid .. " _FindTroopsCargoObject")
|
self:T(self.lid .. " _FindTroopsCargoObject")
|
||||||
|
self._troopsByName = self._troopsByName or {}
|
||||||
|
local cached = self._troopsByName[Name]
|
||||||
|
if cached then
|
||||||
|
return cached
|
||||||
|
end
|
||||||
local cargo = nil
|
local cargo = nil
|
||||||
for _,_cargo in pairs(self.Cargo_Troops)do
|
for _,_cargo in pairs(self.Cargo_Troops)do
|
||||||
local cargo = _cargo -- #CTLD_CARGO
|
local cargo = _cargo -- #CTLD_CARGO
|
||||||
if cargo.Name == Name then
|
if cargo.Name == Name then
|
||||||
|
self._troopsByName[Name] = cargo
|
||||||
return cargo
|
return cargo
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -2411,16 +2472,23 @@ end
|
|||||||
-- @return #CTLD_CARGO Cargo object, nil if it cannot be found
|
-- @return #CTLD_CARGO Cargo object, nil if it cannot be found
|
||||||
function CTLD:_FindCratesCargoObject(Name)
|
function CTLD:_FindCratesCargoObject(Name)
|
||||||
self:T(self.lid .. " _FindCratesCargoObject")
|
self:T(self.lid .. " _FindCratesCargoObject")
|
||||||
|
self._crateOrStaticByName = self._crateOrStaticByName or {}
|
||||||
|
local cached = self._crateOrStaticByName[Name]
|
||||||
|
if cached then
|
||||||
|
return cached
|
||||||
|
end
|
||||||
local cargo = nil
|
local cargo = nil
|
||||||
for _,_cargo in pairs(self.Cargo_Crates)do
|
for _,_cargo in pairs(self.Cargo_Crates)do
|
||||||
local cargo = _cargo -- #CTLD_CARGO
|
local cargo = _cargo -- #CTLD_CARGO
|
||||||
if cargo.Name == Name then
|
if cargo.Name == Name then
|
||||||
|
self._crateOrStaticByName[Name] = cargo
|
||||||
return cargo
|
return cargo
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
for _,_cargo in pairs(self.Cargo_Statics)do
|
for _,_cargo in pairs(self.Cargo_Statics)do
|
||||||
local cargo = _cargo -- #CTLD_CARGO
|
local cargo = _cargo -- #CTLD_CARGO
|
||||||
if cargo.Name == Name then
|
if cargo.Name == Name then
|
||||||
|
self._crateOrStaticByName[Name] = cargo
|
||||||
return cargo
|
return cargo
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -2546,6 +2614,20 @@ function CTLD:PreloadCrates(Unit,Cratesname,NumberOfCrates)
|
|||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- (User) Hook to allow mission-specific troop restrictions.
|
||||||
|
-- Override this in your mission to perform custom checks (e.g. warehouse stock, role limits) before troops are loaded.
|
||||||
|
-- Return true to allow the request, or false to block it. When blocked, _LoadTroops and _LoadTroopsQuantity exit silently.
|
||||||
|
-- @param #CTLD self
|
||||||
|
-- @param Wrapper.Group#GROUP Group Requesting player group.
|
||||||
|
-- @param Wrapper.Unit#UNIT Unit Requesting unit.
|
||||||
|
-- @param #CTLD_CARGO Cargo Troop cargo type being requested.
|
||||||
|
-- @param #number quantity Number of troop sets requested.
|
||||||
|
-- @param #boolean Inject If true, this call originates from an inject/preload path.
|
||||||
|
-- @return #boolean Allow troop loading.
|
||||||
|
function CTLD:CanGetTroops(Group, Unit, Cargo, quantity, Inject)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
--- (Internal) Function to load troops into a heli.
|
--- (Internal) Function to load troops into a heli.
|
||||||
-- @param #CTLD self
|
-- @param #CTLD self
|
||||||
-- @param Wrapper.Group#GROUP Group
|
-- @param Wrapper.Group#GROUP Group
|
||||||
@@ -2616,6 +2698,9 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype, Inject)
|
|||||||
self:_SendMessage("Sorry, that\'s too heavy to load!", 10, false, Group)
|
self:_SendMessage("Sorry, that\'s too heavy to load!", 10, false, Group)
|
||||||
return
|
return
|
||||||
else
|
else
|
||||||
|
if not self:CanGetTroops(Group, Unit, Cargotype, 1, Inject) then
|
||||||
|
return self
|
||||||
|
end
|
||||||
self.CargoCounter = self.CargoCounter + 1
|
self.CargoCounter = self.CargoCounter + 1
|
||||||
local loadcargotype = CTLD_CARGO:New(self.CargoCounter, Cargotype.Name, Cargotype.Templates, cgotype, true, true, Cargotype.CratesNeeded,nil,nil,Cargotype.PerCrateMass)
|
local loadcargotype = CTLD_CARGO:New(self.CargoCounter, Cargotype.Name, Cargotype.Templates, cgotype, true, true, Cargotype.CratesNeeded,nil,nil,Cargotype.PerCrateMass)
|
||||||
self:T({cargotype=loadcargotype})
|
self:T({cargotype=loadcargotype})
|
||||||
@@ -2935,6 +3020,8 @@ function CTLD:_LoadTroopsQuantity(Group, Unit, Cargo, quantity)
|
|||||||
if not self.debug then return self end
|
if not self.debug then return self end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if not self:CanGetTroops(Group, Unit, Cargo, n, false) then return self end
|
||||||
|
|
||||||
local prevSuppress = self.suppressmessages
|
local prevSuppress = self.suppressmessages
|
||||||
self.suppressmessages = true
|
self.suppressmessages = true
|
||||||
for i = 1, n do
|
for i = 1, n do
|
||||||
@@ -3100,15 +3187,29 @@ function CTLD:_AddCrateQuantityMenus(Group, Unit, parentMenu, cargoObj, stockSum
|
|||||||
|
|
||||||
if maxQuantity == 1 then
|
if maxQuantity == 1 then
|
||||||
self:T("_AddCrateQuantityMenus maxQuantity "..maxQuantity.." Menu for MaxQ=1 ".."parentMenu.MenuText = "..parentMenu.MenuText)
|
self:T("_AddCrateQuantityMenus maxQuantity "..maxQuantity.." Menu for MaxQ=1 ".."parentMenu.MenuText = "..parentMenu.MenuText)
|
||||||
--parentMenu.MenuText
|
|
||||||
MENU_GROUP_COMMAND:New(Group, "Get", parentMenu, self._GetCrateQuantity, self, Group, Unit, cargoObj, 1)
|
|
||||||
local canLoad = (allowLoad and (not capacitySets or capacitySets >= 1) and (not maxMassSets or maxMassSets >= 1))
|
local canLoad = (allowLoad and (not capacitySets or capacitySets >= 1) and (not maxMassSets or maxMassSets >= 1))
|
||||||
local isHerc = self:IsC130J(Unit)
|
local isHerc = self:IsC130J(Unit)
|
||||||
local isHook = self:IsHook(Unit)
|
|
||||||
local cgotype = cargoObj:GetType() or nil
|
local cgotype = cargoObj:GetType() or nil
|
||||||
local suppressGetAndLoad = (self.enableChinookGCLoading == true) and isHook and (cgotype == CTLD_CARGO.Enum.STATIC)
|
local suppressGetAndLoad = (self.enableChinookGCLoading == true) and (cgotype == CTLD_CARGO.Enum.STATIC)
|
||||||
local canPartiallyLoad = ((not capacityCrates or capacityCrates >= 1) and (not maxMassCrates or maxMassCrates >= 1))
|
local canPartiallyLoad = ((not capacityCrates or capacityCrates >= 1) and (not maxMassCrates or maxMassCrates >= 1))
|
||||||
|
|
||||||
|
if suppressGetAndLoad or isHerc then
|
||||||
|
if canLoad then
|
||||||
|
MENU_GROUP_COMMAND:New(Group, "1", parentMenu, self._GetCrateQuantity, self, Group, Unit, cargoObj, 1)
|
||||||
|
else
|
||||||
|
local msg
|
||||||
|
if maxMassSets and (not capacitySets or capacitySets >= 1) and maxMassSets < 1 then
|
||||||
|
msg = "Weight limit reached"
|
||||||
|
else
|
||||||
|
msg = "Crate limit reached"
|
||||||
|
end
|
||||||
|
MENU_GROUP_COMMAND:New(Group, msg, parentMenu, self._SendMessage, self, msg, 10, false, Group)
|
||||||
|
end
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
if canLoad and not isHerc and not suppressGetAndLoad then
|
if canLoad and not isHerc and not suppressGetAndLoad then
|
||||||
|
MENU_GROUP_COMMAND:New(Group, "Get", parentMenu, self._GetCrateQuantity, self, Group, Unit, cargoObj, 1)
|
||||||
MENU_GROUP_COMMAND:New(Group, "Get and Load", parentMenu, self._GetAndLoad, self, Group, Unit, cargoObj, 1)
|
MENU_GROUP_COMMAND:New(Group, "Get and Load", parentMenu, self._GetAndLoad, self, Group, Unit, cargoObj, 1)
|
||||||
else
|
else
|
||||||
local msg
|
local msg
|
||||||
@@ -3119,8 +3220,9 @@ function CTLD:_AddCrateQuantityMenus(Group, Unit, parentMenu, cargoObj, stockSum
|
|||||||
msg = "Crate limit reached"
|
msg = "Crate limit reached"
|
||||||
end
|
end
|
||||||
MENU_GROUP_COMMAND:New(Group, msg, parentMenu, self._SendMessage, self, msg, 10, false, Group)
|
MENU_GROUP_COMMAND:New(Group, msg, parentMenu, self._SendMessage, self, msg, 10, false, Group)
|
||||||
|
|
||||||
if canPartiallyLoad and (cgotype ~= CTLD_CARGO.Enum.STATIC) and (not suppressGetAndLoad) then
|
if canPartiallyLoad and (cgotype ~= CTLD_CARGO.Enum.STATIC) and (not suppressGetAndLoad) then
|
||||||
|
MENU_GROUP_COMMAND:New(Group, "Get anyway", parentMenu, self._GetCrateQuantity, self, Group, Unit, cargoObj, 1)
|
||||||
|
|
||||||
MENU_GROUP_COMMAND:New(Group, "Partially load", parentMenu, self._GetAndLoad, self, Group, Unit, cargoObj, 1, true)
|
MENU_GROUP_COMMAND:New(Group, "Partially load", parentMenu, self._GetAndLoad, self, Group, Unit, cargoObj, 1, true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -3133,29 +3235,17 @@ function CTLD:_AddCrateQuantityMenus(Group, Unit, parentMenu, cargoObj, stockSum
|
|||||||
self:T("_AddCrateQuantityMenus maxQuantity "..maxQuantity.." Menu for MaxQ>1")
|
self:T("_AddCrateQuantityMenus maxQuantity "..maxQuantity.." Menu for MaxQ>1")
|
||||||
local label = tostring(quantity)
|
local label = tostring(quantity)
|
||||||
self:T("_AddCrateQuantityMenus Label "..label)
|
self:T("_AddCrateQuantityMenus Label "..label)
|
||||||
local qMenu = MENU_GROUP:New(Group, label, parentMenu)
|
|
||||||
MENU_GROUP_COMMAND:New(Group, "Get", qMenu, self._GetCrateQuantity, self, Group, Unit, cargoObj, quantity)
|
|
||||||
local canLoad = (allowLoad and (not capacitySets or capacitySets >= quantity) and (not maxMassSets or maxMassSets >= quantity))
|
local canLoad = (allowLoad and (not capacitySets or capacitySets >= quantity) and (not maxMassSets or maxMassSets >= quantity))
|
||||||
local isHerc = self:IsC130J(Unit)
|
local isHerc = self:IsC130J(Unit)
|
||||||
local isHook = self:IsHook(Unit)
|
|
||||||
local cgotype = cargoObj:GetType() or nil
|
local cgotype = cargoObj:GetType() or nil
|
||||||
local suppressGetAndLoad = (self.enableChinookGCLoading == true) and isHook and (cgotype == CTLD_CARGO.Enum.STATIC)
|
local suppressGetAndLoad = (self.enableChinookGCLoading == true) and (cgotype == CTLD_CARGO.Enum.STATIC)
|
||||||
local canPartiallyLoad=((not capacityCrates or capacityCrates>=1)and(not maxMassCrates or maxMassCrates>=1))
|
|
||||||
if canLoad and not isHerc and not suppressGetAndLoad then
|
if canLoad and not isHerc and not suppressGetAndLoad then
|
||||||
|
local qMenu = MENU_GROUP:New(Group, label, parentMenu)
|
||||||
|
MENU_GROUP_COMMAND:New(Group, "Get", qMenu, self._GetCrateQuantity, self, Group, Unit, cargoObj, quantity)
|
||||||
MENU_GROUP_COMMAND:New(Group, "Get and Load", qMenu, self._GetAndLoad, self, Group, Unit, cargoObj, quantity)
|
MENU_GROUP_COMMAND:New(Group, "Get and Load", qMenu, self._GetAndLoad, self, Group, Unit, cargoObj, quantity)
|
||||||
else
|
else
|
||||||
local msg
|
MENU_GROUP_COMMAND:New(Group, label, parentMenu, self._GetCrateQuantity, self, Group, Unit, cargoObj, quantity)
|
||||||
if not isHerc and not suppressGetAndLoad then
|
|
||||||
if maxMassSets and (not capacitySets or capacitySets >= quantity) and maxMassSets < quantity then
|
|
||||||
msg = "Weight limit reached"
|
|
||||||
else
|
|
||||||
msg = "Crate limit reached"
|
|
||||||
end
|
|
||||||
MENU_GROUP_COMMAND:New(Group, msg, qMenu, self._SendMessage, self, msg, 10, false, Group)
|
|
||||||
if canPartiallyLoad and (cgotype ~= CTLD_CARGO.Enum.STATIC) and (not suppressGetAndLoad) then
|
|
||||||
MENU_GROUP_COMMAND:New(Group, "Partially load", qMenu, self._GetAndLoad, self, Group, Unit, cargoObj, quantity, true)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return self
|
return self
|
||||||
@@ -3322,7 +3412,6 @@ end
|
|||||||
function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppressGetEvent)
|
function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppressGetEvent)
|
||||||
self:T(self.lid .. " _GetCrates")
|
self:T(self.lid .. " _GetCrates")
|
||||||
|
|
||||||
-- check if we have stock
|
|
||||||
local perSet = Cargo:GetCratesNeeded() or 1
|
local perSet = Cargo:GetCratesNeeded() or 1
|
||||||
if perSet < 1 then perSet = 1 end
|
if perSet < 1 then perSet = 1 end
|
||||||
local requestNumber = tonumber(number)
|
local requestNumber = tonumber(number)
|
||||||
@@ -3338,7 +3427,6 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppress
|
|||||||
local cgoname = Cargo:GetName()
|
local cgoname = Cargo:GetName()
|
||||||
local instock = Cargo:GetStock()
|
local instock = Cargo:GetStock()
|
||||||
if type(instock) == "number" and tonumber(instock) <= 0 and tonumber(instock) ~= -1 then
|
if type(instock) == "number" and tonumber(instock) <= 0 and tonumber(instock) ~= -1 then
|
||||||
-- nothing left over
|
|
||||||
self:_SendMessage(string.format("Sorry, we ran out of %s", cgoname), 10, false, Group)
|
self:_SendMessage(string.format("Sorry, we ran out of %s", cgoname), 10, false, Group)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
@@ -3355,7 +3443,6 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppress
|
|||||||
if not drop and not pack then
|
if not drop and not pack then
|
||||||
inzone = self:IsUnitInZone(Unit, CTLD.CargoZoneType.LOAD)
|
inzone = self:IsUnitInZone(Unit, CTLD.CargoZoneType.LOAD)
|
||||||
if not inzone then
|
if not inzone then
|
||||||
---@diagnostic disable-next-line: cast-local-type
|
|
||||||
inzone, ship, zone, distance, width = self:IsUnitInZone(Unit, CTLD.CargoZoneType.SHIP)
|
inzone, ship, zone, distance, width = self:IsUnitInZone(Unit, CTLD.CargoZoneType.SHIP)
|
||||||
end
|
end
|
||||||
elseif drop and not pack then
|
elseif drop and not pack then
|
||||||
@@ -3390,23 +3477,25 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppress
|
|||||||
local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitTypeCapabilities
|
local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitTypeCapabilities
|
||||||
local canloadcratesno = capabilities.cratelimit
|
local canloadcratesno = capabilities.cratelimit
|
||||||
local loaddist = self.CrateDistance or 35
|
local loaddist = self.CrateDistance or 35
|
||||||
local nearcrates, numbernearby = self:_FindCratesNearby(Group,Unit,loaddist,true,true,true) -- to ignore what's inside
|
local nearcrates, numbernearby = self:_FindCratesNearby(Group, Unit, loaddist, true, true, true)
|
||||||
if numbernearby >= canloadcratesno and not drop then
|
if numbernearby >= canloadcratesno and not drop then
|
||||||
self:_SendMessage("There are enough crates nearby already! Take care of those first!", 10, false, Group)
|
self:_SendMessage("There are enough crates nearby already! Take care of those first!", 10, false, Group)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
if not self:CanGetCrates(Group, Unit, Cargo, requestNumber, drop, pack, quiet, suppressGetEvent) then
|
if not drop and not self:CanGetCrates(Group, Unit, Cargo, requestNumber, drop, pack, quiet, suppressGetEvent) then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
-- spawn crates in front of helicopter
|
-- spawn crates in front of helicopter
|
||||||
local IsHerc = self:IsFixedWing(Unit) -- Herc, Bronco and Hook load from behind
|
local IsHerc = self:IsFixedWing(Unit) -- Herc, Bronco and Hook load from behind
|
||||||
local IsHook = self:IsHook(Unit) -- Herc, Bronco and Hook load from behind
|
local IsHook = self:IsHook(Unit) -- Herc, Bronco and Hook load from behind
|
||||||
|
local IsHelo = Unit and Unit.IsHelicopter and Unit:IsHelicopter() or false
|
||||||
local IsTruck = Unit:IsGround()
|
local IsTruck = Unit:IsGround()
|
||||||
local cargotype = Cargo -- Ops.CTLD#CTLD_CARGO
|
local cargotype = Cargo -- Ops.CTLD#CTLD_CARGO
|
||||||
local number = requestNumber --#number
|
local number = requestNumber --#number
|
||||||
local cratesneeded = cargotype:GetCratesNeeded() --#number
|
local cratesneeded = cargotype:GetCratesNeeded() --#number
|
||||||
local cratename = cargotype:GetName()
|
local cratename = cargotype:GetName()
|
||||||
|
local cratedisplayname = self:_GetCargoDisplayName(cargotype)
|
||||||
local cratetemplate = "Container"-- #string
|
local cratetemplate = "Container"-- #string
|
||||||
local cgotype = cargotype:GetType()
|
local cgotype = cargotype:GetType()
|
||||||
local cgomass = cargotype:GetMass()
|
local cgomass = cargotype:GetMass()
|
||||||
@@ -3433,28 +3522,123 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppress
|
|||||||
heading = (heading + addon) % 360
|
heading = (heading + addon) % 360
|
||||||
local row = 1
|
local row = 1
|
||||||
local column = 1
|
local column = 1
|
||||||
local initialdist = IsHerc and 16 or (capabilities.length+2) -- initial spacing of the first crates
|
local initialdist = IsHerc and 16 or (capabilities.length + 2)
|
||||||
local startpos = position:Translate(initialdist, heading)
|
local startpos = position:Translate(initialdist, heading)
|
||||||
if self.placeCratesAhead == true then
|
if self.placeCratesAhead == true then
|
||||||
cratedistance = initialdist
|
cratedistance = initialdist
|
||||||
end
|
end
|
||||||
|
|
||||||
-- loop crates needed
|
local cratecoord = nil
|
||||||
local cratecoord = nil -- Core.Point#COORDINATE
|
local shipdist = nil
|
||||||
for i=1,number do
|
local shipoffset = nil
|
||||||
local cratealias = string.format("%s-%s-%d", cratename, cratetemplate, math.random(1,100000))
|
|
||||||
if not self.placeCratesAhead or drop == true then
|
local FW_STEP_BY_TYPE = {
|
||||||
cratedistance = (i-1)*2.5 + capabilities.length
|
cds_crate = 2.0,
|
||||||
if cratedistance > self.CrateDistance then cratedistance = self.CrateDistance end
|
cds_barrels = 2.0,
|
||||||
-- altered heading logic
|
ammo_cargo = 1.0,
|
||||||
-- DONE: right standard deviation?
|
iso_container_small = 3.5,
|
||||||
if self:IsUnitInAir(Unit) and self:IsFixedWing(Unit) then
|
iso_container = 3.5,
|
||||||
rheading = math.random(20,60)
|
uh1h_cargo = 2.0,
|
||||||
else
|
container_cargo = 2.5,
|
||||||
rheading = UTILS.RandomGaussian(0, 30, -90, 90, 100)
|
}
|
||||||
|
local FW_WIDTH_BY_TYPE = { -- spacing between crates in the set.
|
||||||
|
cds_crate = 0.8,
|
||||||
|
cds_barrels = 0.8,
|
||||||
|
ammo_cargo = 0.4,
|
||||||
|
iso_container_small = 2.0,
|
||||||
|
iso_container = 2.0,
|
||||||
|
uh1h_cargo = 0.6,
|
||||||
|
container_cargo = 1.3,
|
||||||
|
}
|
||||||
|
local FW_ROW_GAP = 0.6
|
||||||
|
local FW_BATCH_ANGLE_PATTERN = { 0, -20, 20, -40, 40, -60, 60, -80, 80 }
|
||||||
|
local fwBatchState = nil
|
||||||
|
local fwBatchIndex = 0
|
||||||
|
local fwBatchKey = nil
|
||||||
|
if IsHerc or IsHelo then
|
||||||
|
self._fwBatchState = self._fwBatchState or {}
|
||||||
|
fwBatchKey = (Unit and Unit.GetName and Unit:GetName()) or "FW"
|
||||||
|
fwBatchState = self._fwBatchState[fwBatchKey] or { batch = 0 }
|
||||||
|
if (tonumber(numbernearby) or 0) <= 0 then
|
||||||
|
fwBatchState.batch = 0
|
||||||
end
|
end
|
||||||
|
fwBatchIndex = fwBatchState.batch or 0
|
||||||
|
end
|
||||||
|
|
||||||
|
local fwZeroAngleSetHeading = nil
|
||||||
|
local fwNonZeroAngleSetHeading = nil
|
||||||
|
|
||||||
|
for i = 1, number do
|
||||||
|
local currentAngleOffset = 0
|
||||||
|
local cratealias = string.format("%s-%d", cratename, math.random(1, 100000))
|
||||||
|
local CCat, CType, CShape = Cargo:GetStaticTypeAndShape()
|
||||||
|
local basetype = CType or self.basetype or "container_cargo"
|
||||||
|
CCat = CCat or "Cargos"
|
||||||
|
|
||||||
|
if not isstatic and self:IsC130J(Unit, true) then
|
||||||
|
if Cargo.C130TypeName then
|
||||||
|
basetype = Cargo.C130TypeName
|
||||||
|
elseif self.C130basetype and (not CType or CType == self.basetype) then
|
||||||
|
basetype = self.C130basetype
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not self.placeCratesAhead or drop == true then
|
||||||
|
local step = (IsHerc or IsHelo) and (FW_STEP_BY_TYPE[basetype] or 2.6) or 1.6
|
||||||
|
if (IsHerc or IsHelo) and not drop then
|
||||||
|
local safeDistance = capabilities.length * 0.9
|
||||||
|
local maxDist = self.CrateDistance or 35
|
||||||
|
if safeDistance > maxDist then safeDistance = maxDist end
|
||||||
|
|
||||||
|
local angleIndex = (fwBatchIndex % #FW_BATCH_ANGLE_PATTERN) + 1
|
||||||
|
currentAngleOffset = FW_BATCH_ANGLE_PATTERN[angleIndex] or 0
|
||||||
|
local centerHeading = math.fmod((heading + currentAngleOffset), 360)
|
||||||
|
if math.abs(currentAngleOffset) >= 0.01 and not fwNonZeroAngleSetHeading then
|
||||||
|
fwNonZeroAngleSetHeading = centerHeading
|
||||||
|
end
|
||||||
|
|
||||||
|
local baseDistance = safeDistance
|
||||||
|
local crateWidth = FW_WIDTH_BY_TYPE[basetype] or step
|
||||||
|
local zeroAngle = math.abs(currentAngleOffset) < 0.01
|
||||||
|
local lateral = nil
|
||||||
|
local lateralHeading = nil
|
||||||
|
if zeroAngle then
|
||||||
|
local totalRowWidth = (number * crateWidth) + (math.max(0, number - 1) * FW_ROW_GAP)
|
||||||
|
local leftEdge = -(totalRowWidth / 2)
|
||||||
|
lateral = leftEdge + ((i - 1) * (crateWidth + FW_ROW_GAP)) + (crateWidth / 2)
|
||||||
|
if lateral >= 0 then
|
||||||
|
lateralHeading = math.fmod(centerHeading + 90, 360)
|
||||||
|
else
|
||||||
|
lateral = -lateral
|
||||||
|
lateralHeading = math.fmod(centerHeading + 270, 360)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
lateral = (i - 1) * (crateWidth + FW_ROW_GAP)
|
||||||
|
if currentAngleOffset < 0 then
|
||||||
|
lateralHeading = math.fmod(centerHeading + 270, 360)
|
||||||
|
else
|
||||||
|
lateralHeading = math.fmod(centerHeading + 90, 360)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local maxLateralSq = (maxDist * maxDist) - (baseDistance * baseDistance)
|
||||||
|
if maxLateralSq < 0 then maxLateralSq = 0 end
|
||||||
|
local maxLateral = math.sqrt(maxLateralSq)
|
||||||
|
if lateral > maxLateral then
|
||||||
|
lateral = maxLateral
|
||||||
|
end
|
||||||
|
|
||||||
|
local baseCoord = position:Translate(baseDistance, centerHeading)
|
||||||
|
cratecoord = baseCoord:Translate(lateral, lateralHeading)
|
||||||
|
cratedistance = baseDistance
|
||||||
|
rheading = centerHeading
|
||||||
|
else
|
||||||
|
cratedistance = (i - 1) * step + capabilities.length
|
||||||
|
if cratedistance > self.CrateDistance then cratedistance = self.CrateDistance end
|
||||||
|
rheading = UTILS.RandomGaussian(0, 18, -55, 55, 100)
|
||||||
rheading = math.fmod((heading + rheading), 360)
|
rheading = math.fmod((heading + rheading), 360)
|
||||||
cratecoord = position:Translate(cratedistance, rheading)
|
cratecoord = position:Translate(cratedistance, rheading)
|
||||||
|
end
|
||||||
else
|
else
|
||||||
cratedistance = (row - 1) * 6
|
cratedistance = (row - 1) * 6
|
||||||
rheading = 90
|
rheading = 90
|
||||||
@@ -3466,18 +3650,19 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppress
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--local cratevec2 = cratecoord:GetVec2()
|
|
||||||
self.CrateCounter = self.CrateCounter + 1
|
self.CrateCounter = self.CrateCounter + 1
|
||||||
local CCat, CType, CShape = Cargo:GetStaticTypeAndShape()
|
local crateSpawnHeading = 270
|
||||||
local basetype = CType or self.basetype or "container_cargo"
|
if (IsHerc or IsHelo) and not drop and cratecoord and type(ship) ~= "string" then
|
||||||
CCat = CCat or "Cargos"
|
if math.abs(currentAngleOffset) < 0.01 then
|
||||||
if not isstatic and self:IsC130J(Unit) then
|
if not fwZeroAngleSetHeading then
|
||||||
if Cargo.C130TypeName then
|
fwZeroAngleSetHeading = heading
|
||||||
basetype = Cargo.C130TypeName
|
end
|
||||||
elseif self.C130basetype and (not CType or CType == self.basetype) then
|
crateSpawnHeading = fwZeroAngleSetHeading
|
||||||
basetype = self.C130basetype
|
else
|
||||||
|
crateSpawnHeading = fwNonZeroAngleSetHeading
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if type(ship) == "string" then
|
if type(ship) == "string" then
|
||||||
self:T("Spawning on ship "..ship)
|
self:T("Spawning on ship "..ship)
|
||||||
local Ship = UNIT:FindByName(ship)
|
local Ship = UNIT:FindByName(ship)
|
||||||
@@ -3485,8 +3670,26 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppress
|
|||||||
local unitcoord = Unit:GetCoordinate()
|
local unitcoord = Unit:GetCoordinate()
|
||||||
local dist = shipcoord:Get2DDistance(unitcoord)
|
local dist = shipcoord:Get2DDistance(unitcoord)
|
||||||
dist = dist - (20 + math.random(1, 10))
|
dist = dist - (20 + math.random(1, 10))
|
||||||
local width = width / 2
|
local halfwidth = (width or 20) / 2
|
||||||
local Offy = math.random(-width,width)
|
local Offy = nil
|
||||||
|
if i == 1 or shipdist == nil or shipoffset == nil then
|
||||||
|
Offy = math.random(-halfwidth, halfwidth)
|
||||||
|
shipoffset = Offy
|
||||||
|
shipdist = dist
|
||||||
|
else
|
||||||
|
dist = shipdist
|
||||||
|
local step = math.max(1, math.min(3, halfwidth * 0.2))
|
||||||
|
local slot = i - 1
|
||||||
|
local ring = math.floor((slot + 1) / 2)
|
||||||
|
local sign = (slot % 2 == 1) and 1 or -1
|
||||||
|
Offy = shipoffset + (sign * ring * step)
|
||||||
|
if Offy > halfwidth then
|
||||||
|
Offy = halfwidth
|
||||||
|
elseif Offy < -halfwidth then
|
||||||
|
Offy = -halfwidth
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local spawnstatic = SPAWNSTATIC:NewFromType(basetype, CCat, self.cratecountry)
|
local spawnstatic = SPAWNSTATIC:NewFromType(basetype, CCat, self.cratecountry)
|
||||||
:InitCargoMass(cgomass)
|
:InitCargoMass(cgomass)
|
||||||
:InitCargo(self.enableslingload)
|
:InitCargo(self.enableslingload)
|
||||||
@@ -3498,7 +3701,7 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppress
|
|||||||
local map = cargotype:GetStaticResourceMap()
|
local map = cargotype:GetStaticResourceMap()
|
||||||
spawnstatic.TemplateStaticUnit.resourcePayload = map
|
spawnstatic.TemplateStaticUnit.resourcePayload = map
|
||||||
end
|
end
|
||||||
self.Spawned_Crates[self.CrateCounter] = spawnstatic:Spawn(270,cratealias)
|
self.Spawned_Crates[self.CrateCounter] = spawnstatic:Spawn(crateSpawnHeading, cratealias)
|
||||||
else
|
else
|
||||||
local spawnstatic = SPAWNSTATIC:NewFromType(basetype, CCat, self.cratecountry)
|
local spawnstatic = SPAWNSTATIC:NewFromType(basetype, CCat, self.cratecountry)
|
||||||
:InitCoordinate(cratecoord)
|
:InitCoordinate(cratecoord)
|
||||||
@@ -3511,8 +3714,9 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppress
|
|||||||
local map = cargotype:GetStaticResourceMap()
|
local map = cargotype:GetStaticResourceMap()
|
||||||
spawnstatic.TemplateStaticUnit.resourcePayload = map
|
spawnstatic.TemplateStaticUnit.resourcePayload = map
|
||||||
end
|
end
|
||||||
self.Spawned_Crates[self.CrateCounter] = spawnstatic:Spawn(270,cratealias)
|
self.Spawned_Crates[self.CrateCounter] = spawnstatic:Spawn(crateSpawnHeading, cratealias)
|
||||||
end
|
end
|
||||||
|
|
||||||
local templ = cargotype:GetTemplates()
|
local templ = cargotype:GetTemplates()
|
||||||
local sorte = cargotype:GetType()
|
local sorte = cargotype:GetType()
|
||||||
local subcat = cargotype.Subcategory
|
local subcat = cargotype.Subcategory
|
||||||
@@ -3520,7 +3724,8 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppress
|
|||||||
local realcargo = nil
|
local realcargo = nil
|
||||||
if drop then
|
if drop then
|
||||||
--CTLD_CARGO:New(ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped, PerCrateMass, Stock, Subcategory)
|
--CTLD_CARGO:New(ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped, PerCrateMass, Stock, Subcategory)
|
||||||
realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,true,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true,cargotype.PerCrateMass,nil,subcat) -- #CTLD_CARGO
|
realcargo = CTLD_CARGO:New(self.CargoCounter, cratename, templ, sorte, true, false, cratesneeded, self.Spawned_Crates[self.CrateCounter], true, cargotype.PerCrateMass, nil, subcat)
|
||||||
|
realcargo:SetDisplayName(cargotype:GetDisplayName())
|
||||||
local map = cargotype:GetStaticResourceMap()
|
local map = cargotype:GetStaticResourceMap()
|
||||||
realcargo:SetStaticResourceMap(map)
|
realcargo:SetStaticResourceMap(map)
|
||||||
local CCat3, CType3, CShape3 = cargotype:GetStaticTypeAndShape()
|
local CCat3, CType3, CShape3 = cargotype:GetStaticTypeAndShape()
|
||||||
@@ -3531,15 +3736,17 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppress
|
|||||||
table.insert(droppedcargo, realcargo)
|
table.insert(droppedcargo, realcargo)
|
||||||
else
|
else
|
||||||
realcargo = CTLD_CARGO:New(self.CargoCounter, cratename, templ, sorte, false, false, cratesneeded, self.Spawned_Crates[self.CrateCounter], false, cargotype.PerCrateMass, nil, subcat)
|
realcargo = CTLD_CARGO:New(self.CargoCounter, cratename, templ, sorte, false, false, cratesneeded, self.Spawned_Crates[self.CrateCounter], false, cargotype.PerCrateMass, nil, subcat)
|
||||||
|
realcargo:SetDisplayName(cargotype:GetDisplayName())
|
||||||
local map = cargotype:GetStaticResourceMap()
|
local map = cargotype:GetStaticResourceMap()
|
||||||
realcargo:SetStaticResourceMap(map)
|
realcargo:SetStaticResourceMap(map)
|
||||||
if cargotype.TypeNames then
|
if cargotype.TypeNames then
|
||||||
realcargo.TypeNames = UTILS.DeepCopy(cargotype.TypeNames)
|
realcargo.TypeNames = UTILS.DeepCopy(cargotype.TypeNames)
|
||||||
end
|
end
|
||||||
if self.UseC130LoadAndUnload and self:IsC130J(Unit) then
|
if self.UseC130LoadAndUnload and self:IsC130J(Unit) then
|
||||||
realcargo:SetWasDropped(true,true) -- we mark here that the crates was dropped even though we just got them because of the herc.
|
realcargo:SetWasDropped(true, true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if not drop and not pack then
|
if not drop and not pack then
|
||||||
table.insert(obtainedcargo, realcargo)
|
table.insert(obtainedcargo, realcargo)
|
||||||
end
|
end
|
||||||
@@ -3548,13 +3755,19 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppress
|
|||||||
table.insert(self.Spawned_Cargo, realcargo)
|
table.insert(self.Spawned_Cargo, realcargo)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if (IsHerc or IsHelo) and fwBatchState and fwBatchKey and not drop then
|
||||||
|
local maxBatches = #FW_BATCH_ANGLE_PATTERN
|
||||||
|
fwBatchState.batch = (fwBatchIndex + 1) % maxBatches
|
||||||
|
self._fwBatchState[fwBatchKey] = fwBatchState
|
||||||
|
end
|
||||||
|
|
||||||
if not (drop or pack) then
|
if not (drop or pack) then
|
||||||
Cargo:RemoveStock(requestedSets)
|
Cargo:RemoveStock(requestedSets)
|
||||||
self:_RefreshCrateQuantityMenus(Group, Unit, Cargo)
|
self:_RefreshCrateQuantityMenus(Group, Unit, Cargo)
|
||||||
end
|
end
|
||||||
local text = string.format("%d crates for %s have been positioned near you!",number,cratename)
|
local text = string.format("%d crates for %s have been positioned near you!", number, cratedisplayname)
|
||||||
if drop then
|
if drop then
|
||||||
text = string.format("%d crates for %s have been dropped!",number,cratename)
|
text = string.format("%d crates for %s have been dropped!", number, cratedisplayname)
|
||||||
self:__CratesDropped(1, Group, Unit, droppedcargo)
|
self:__CratesDropped(1, Group, Unit, droppedcargo)
|
||||||
else
|
else
|
||||||
if not quiet then
|
if not quiet then
|
||||||
@@ -3877,7 +4090,7 @@ function CTLD:_FindCratesNearby( _group, _unit, _dist, _ignoreweight, ignoretype
|
|||||||
self:T(self.lid..string.format("Dist %dm/%dm | weight %dkg | maxloadable %dkg",distance,finddist,weight,maxloadable))
|
self:T(self.lid..string.format("Dist %dm/%dm | weight %dkg | maxloadable %dkg",distance,finddist,weight,maxloadable))
|
||||||
if distance<=finddist and(weight<=maxloadable or _ignoreweight)and restricted==false and cando==true and not hercInnerBlocked then
|
if distance<=finddist and(weight<=maxloadable or _ignoreweight)and restricted==false and cando==true and not hercInnerBlocked then
|
||||||
index = index + 1
|
index = index + 1
|
||||||
table.insert(found, staticid, cargo)
|
found[#found+1] = cargo
|
||||||
maxloadable = maxloadable - weight
|
maxloadable = maxloadable - weight
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -4026,22 +4239,21 @@ end
|
|||||||
function CTLD:_CleanupTrackedCrates(crateIdsToRemove)
|
function CTLD:_CleanupTrackedCrates(crateIdsToRemove)
|
||||||
local existingcrates = self.Spawned_Cargo -- #table
|
local existingcrates = self.Spawned_Cargo -- #table
|
||||||
local newexcrates = {}
|
local newexcrates = {}
|
||||||
|
local remove = {}
|
||||||
|
for _,_ID in pairs(crateIdsToRemove or {}) do
|
||||||
|
remove[_ID] = true
|
||||||
|
end
|
||||||
for _,_crate in pairs(existingcrates) do
|
for _,_crate in pairs(existingcrates) do
|
||||||
local excrate = _crate -- #CTLD_CARGO
|
local excrate = _crate -- #CTLD_CARGO
|
||||||
local ID = excrate:GetID()
|
local ID = excrate:GetID()
|
||||||
local keep = true
|
local keep = not remove[ID]
|
||||||
for _,_ID in pairs(crateIdsToRemove) do
|
|
||||||
if ID == _ID then
|
|
||||||
keep = false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- remove destroyed crates here too
|
-- remove destroyed crates here too
|
||||||
local static = _crate:GetPositionable() -- Wrapper.Static#STATIC -- crates
|
local static = _crate:GetPositionable() -- Wrapper.Static#STATIC -- crates
|
||||||
if not static or not static:IsAlive() then
|
if not static or not static:IsAlive() then
|
||||||
keep = false
|
keep = false
|
||||||
end
|
end
|
||||||
if keep then
|
if keep then
|
||||||
table.insert(newexcrates,_crate)
|
newexcrates[#newexcrates+1] = _crate
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
self.Spawned_Cargo = nil
|
self.Spawned_Cargo = nil
|
||||||
@@ -5592,7 +5804,7 @@ function CTLD:_RefreshF10Menus()
|
|||||||
end
|
end
|
||||||
for _, cargoObj in pairs(self.Cargo_Troops) do
|
for _, cargoObj in pairs(self.Cargo_Troops) do
|
||||||
if not cargoObj.DontShowInMenu then
|
if not cargoObj.DontShowInMenu then
|
||||||
local menutext = cargoObj.Name
|
local menutext = self:_GetCargoDisplayName(cargoObj)
|
||||||
local parent = troopsmenu
|
local parent = troopsmenu
|
||||||
if useTroopSubcats and cargoObj.Subcategory and subcatmenus[cargoObj.Subcategory] then
|
if useTroopSubcats and cargoObj.Subcategory and subcatmenus[cargoObj.Subcategory] then
|
||||||
parent = subcatmenus[cargoObj.Subcategory]
|
parent = subcatmenus[cargoObj.Subcategory]
|
||||||
@@ -5605,7 +5817,7 @@ function CTLD:_RefreshF10Menus()
|
|||||||
else
|
else
|
||||||
for _, cargoObj in pairs(self.Cargo_Troops) do
|
for _, cargoObj in pairs(self.Cargo_Troops) do
|
||||||
if not cargoObj.DontShowInMenu then
|
if not cargoObj.DontShowInMenu then
|
||||||
local menutext = cargoObj.Name
|
local menutext = self:_GetCargoDisplayName(cargoObj)
|
||||||
local mSet = MENU_GROUP:New(_group, menutext, troopsmenu)
|
local mSet = MENU_GROUP:New(_group, menutext, troopsmenu)
|
||||||
_group.CTLD_TroopMenus[cargoObj.Name] = mSet
|
_group.CTLD_TroopMenus[cargoObj.Name] = mSet
|
||||||
self:_AddTroopQuantityMenus(_group,_unit,mSet,cargoObj)
|
self:_AddTroopQuantityMenus(_group,_unit,mSet,cargoObj)
|
||||||
@@ -5620,7 +5832,7 @@ function CTLD:_RefreshF10Menus()
|
|||||||
if loadedData and loadedData.Cargo then
|
if loadedData and loadedData.Cargo then
|
||||||
for i,cargoObj in ipairs(loadedData.Cargo) do
|
for i,cargoObj in ipairs(loadedData.Cargo) do
|
||||||
if cargoObj and (cargoObj:GetType()==CTLD_CARGO.Enum.TROOPS or cargoObj:GetType()==CTLD_CARGO.Enum.ENGINEERS) and not cargoObj:WasDropped() then
|
if cargoObj and (cargoObj:GetType()==CTLD_CARGO.Enum.TROOPS or cargoObj:GetType()==CTLD_CARGO.Enum.ENGINEERS) and not cargoObj:WasDropped() then
|
||||||
local name=cargoObj:GetName() or "Unknown"
|
local name=self:_GetCargoDisplayName(cargoObj)
|
||||||
local needed=cargoObj:GetCratesNeeded() or 1
|
local needed=cargoObj:GetCratesNeeded() or 1
|
||||||
local cID=cargoObj:GetID()
|
local cID=cargoObj:GetID()
|
||||||
local line=string.format("Drop: %s",name,needed,cID)
|
local line=string.format("Drop: %s",name,needed,cID)
|
||||||
@@ -5673,10 +5885,11 @@ function CTLD:_RefreshF10Menus()
|
|||||||
local needed = cargoObj:GetCratesNeeded() or 1
|
local needed = cargoObj:GetCratesNeeded() or 1
|
||||||
|
|
||||||
local txt
|
local txt
|
||||||
|
local cargoLabel = self:_GetCargoDisplayName(cargoObj)
|
||||||
if needed > 1 then
|
if needed > 1 then
|
||||||
txt = string.format("%d crate%s %s (%dkg)",needed,needed==1 and "" or "s",cargoObj.Name,cargoObj.PerCrateMass or 0)
|
txt = string.format("%d crate%s %s (%dkg)",needed,needed==1 and "" or "s",cargoLabel,cargoObj.PerCrateMass or 0)
|
||||||
else
|
else
|
||||||
txt = string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0)
|
txt = string.format("%s (%dkg)",cargoLabel,cargoObj.PerCrateMass or 0)
|
||||||
end
|
end
|
||||||
if cargoObj.Location then txt = txt.."[R]" end
|
if cargoObj.Location then txt = txt.."[R]" end
|
||||||
if self.showstockinmenuitems then
|
if self.showstockinmenuitems then
|
||||||
@@ -5721,10 +5934,11 @@ function CTLD:_RefreshF10Menus()
|
|||||||
if not cargoObj.DontShowInMenu then
|
if not cargoObj.DontShowInMenu then
|
||||||
local needed = cargoObj:GetCratesNeeded() or 1
|
local needed = cargoObj:GetCratesNeeded() or 1
|
||||||
local txt
|
local txt
|
||||||
|
local cargoLabel = self:_GetCargoDisplayName(cargoObj)
|
||||||
if needed > 1 then
|
if needed > 1 then
|
||||||
txt = string.format("%d crate%s %s (%dkg)",needed,needed==1 and "" or "s",cargoObj.Name,cargoObj.PerCrateMass or 0)
|
txt = string.format("%d crate%s %s (%dkg)",needed,needed==1 and "" or "s",cargoLabel,cargoObj.PerCrateMass or 0)
|
||||||
else
|
else
|
||||||
txt = string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0)
|
txt = string.format("%s (%dkg)",cargoLabel,cargoObj.PerCrateMass or 0)
|
||||||
end
|
end
|
||||||
if cargoObj.Location then txt = txt.."[R]" end
|
if cargoObj.Location then txt = txt.."[R]" end
|
||||||
local stock = cargoObj:GetStock()
|
local stock = cargoObj:GetStock()
|
||||||
@@ -5736,10 +5950,11 @@ function CTLD:_RefreshF10Menus()
|
|||||||
if (not cargoObj.DontShowInMenu) and (not cargoObj.UnitCanCarry or cargoObj:UnitCanCarry(_unit)) then
|
if (not cargoObj.DontShowInMenu) and (not cargoObj.UnitCanCarry or cargoObj:UnitCanCarry(_unit)) then
|
||||||
local needed = cargoObj:GetCratesNeeded() or 1
|
local needed = cargoObj:GetCratesNeeded() or 1
|
||||||
local txt
|
local txt
|
||||||
|
local cargoLabel = self:_GetCargoDisplayName(cargoObj)
|
||||||
if needed > 1 then
|
if needed > 1 then
|
||||||
txt = string.format("%d crate%s %s (%dkg)",needed,needed==1 and "" or "s",cargoObj.Name,cargoObj.PerCrateMass or 0)
|
txt = string.format("%d crate%s %s (%dkg)",needed,needed==1 and "" or "s",cargoLabel,cargoObj.PerCrateMass or 0)
|
||||||
else
|
else
|
||||||
txt = string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0)
|
txt = string.format("%s (%dkg)",cargoLabel,cargoObj.PerCrateMass or 0)
|
||||||
end
|
end
|
||||||
if cargoObj.Location then txt = txt.."[R]" end
|
if cargoObj.Location then txt = txt.."[R]" end
|
||||||
local stock = cargoObj:GetStock()
|
local stock = cargoObj:GetStock()
|
||||||
@@ -5752,10 +5967,11 @@ function CTLD:_RefreshF10Menus()
|
|||||||
if not cargoObj.DontShowInMenu then
|
if not cargoObj.DontShowInMenu then
|
||||||
local needed = cargoObj:GetCratesNeeded() or 1
|
local needed = cargoObj:GetCratesNeeded() or 1
|
||||||
local txt
|
local txt
|
||||||
|
local cargoLabel = self:_GetCargoDisplayName(cargoObj)
|
||||||
if needed > 1 then
|
if needed > 1 then
|
||||||
txt = string.format("%d crate%s %s (%dkg)",needed,needed==1 and "" or "s",cargoObj.Name,cargoObj.PerCrateMass or 0)
|
txt = string.format("%d crate%s %s (%dkg)",needed,needed==1 and "" or "s",cargoLabel,cargoObj.PerCrateMass or 0)
|
||||||
else
|
else
|
||||||
txt = string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0)
|
txt = string.format("%s (%dkg)",cargoLabel,cargoObj.PerCrateMass or 0)
|
||||||
end
|
end
|
||||||
if cargoObj.Location then txt = txt.."[R]" end
|
if cargoObj.Location then txt = txt.."[R]" end
|
||||||
local stock = cargoObj:GetStock()
|
local stock = cargoObj:GetStock()
|
||||||
@@ -5767,10 +5983,11 @@ function CTLD:_RefreshF10Menus()
|
|||||||
if (not cargoObj.DontShowInMenu) and (not cargoObj.UnitCanCarry or cargoObj:UnitCanCarry(_unit)) then
|
if (not cargoObj.DontShowInMenu) and (not cargoObj.UnitCanCarry or cargoObj:UnitCanCarry(_unit)) then
|
||||||
local needed = cargoObj:GetCratesNeeded() or 1
|
local needed = cargoObj:GetCratesNeeded() or 1
|
||||||
local txt
|
local txt
|
||||||
|
local cargoLabel = self:_GetCargoDisplayName(cargoObj)
|
||||||
if needed > 1 then
|
if needed > 1 then
|
||||||
txt = string.format("%d crate%s %s (%dkg)",needed,needed==1 and "" or "s",cargoObj.Name,cargoObj.PerCrateMass or 0)
|
txt = string.format("%d crate%s %s (%dkg)",needed,needed==1 and "" or "s",cargoLabel,cargoObj.PerCrateMass or 0)
|
||||||
else
|
else
|
||||||
txt = string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0)
|
txt = string.format("%s (%dkg)",cargoLabel,cargoObj.PerCrateMass or 0)
|
||||||
end
|
end
|
||||||
if cargoObj.Location then txt = txt.."[R]" end
|
if cargoObj.Location then txt = txt.."[R]" end
|
||||||
local stock = cargoObj:GetStock()
|
local stock = cargoObj:GetStock()
|
||||||
@@ -5816,12 +6033,13 @@ function CTLD:_RefreshF10Menus()
|
|||||||
if cgo and (not cgo:WasDropped()) then
|
if cgo and (not cgo:WasDropped()) then
|
||||||
local cname = cgo:GetName()
|
local cname = cgo:GetName()
|
||||||
local cneeded = cgo:GetCratesNeeded()
|
local cneeded = cgo:GetCratesNeeded()
|
||||||
cargoByName[cname] = cargoByName[cname] or { count=0, needed=cneeded }
|
local cdisplay = self:_GetCargoDisplayName(cgo)
|
||||||
|
cargoByName[cname] = cargoByName[cname] or { count=0, needed=cneeded, display=cdisplay }
|
||||||
cargoByName[cname].count = cargoByName[cname].count + 1
|
cargoByName[cname].count = cargoByName[cname].count + 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
for name, info in pairs(cargoByName) do
|
for name, info in pairs(cargoByName) do
|
||||||
local line = string.format("Drop %s (%d/%d)", name, info.count, info.needed)
|
local line = string.format("Drop %s (%d/%d)", info.display or name, info.count, info.needed)
|
||||||
MENU_GROUP_COMMAND:New(_group, line, dropCratesMenu, self._UnloadSingleCrateSet, self, _group, _unit, name)
|
MENU_GROUP_COMMAND:New(_group, line, dropCratesMenu, self._UnloadSingleCrateSet, self, _group, _unit, name)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -5857,7 +6075,7 @@ function CTLD:_RefreshF10Menus()
|
|||||||
end
|
end
|
||||||
parent = sub
|
parent = sub
|
||||||
end
|
end
|
||||||
local menutext = cargoObj.Name
|
local menutext = self:_GetCargoDisplayName(cargoObj)
|
||||||
if type(cargoObj.Stock) == "number" and cargoObj.Stock >= 0 and self.showstockinmenuitems then
|
if type(cargoObj.Stock) == "number" and cargoObj.Stock >= 0 and self.showstockinmenuitems then
|
||||||
menutext = menutext.."["..cargoObj.Stock.."]"
|
menutext = menutext.."["..cargoObj.Stock.."]"
|
||||||
end
|
end
|
||||||
@@ -6758,6 +6976,16 @@ function CTLD:AddTroopsCargo(Name,Templates,Type,NoTroops,PerTroopMass,Stock,Sub
|
|||||||
-- Troops are directly loadable
|
-- Troops are directly loadable
|
||||||
local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,true,NoTroops,nil,nil,PerTroopMass,Stock, SubCategory)
|
local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,true,NoTroops,nil,nil,PerTroopMass,Stock, SubCategory)
|
||||||
table.insert(self.Cargo_Troops,cargo)
|
table.insert(self.Cargo_Troops,cargo)
|
||||||
|
self._troopsByName = self._troopsByName or {}
|
||||||
|
self._troopsByName[cargo.Name] = cargo
|
||||||
|
self._cargoByTemplate = self._cargoByTemplate or {}
|
||||||
|
local _template = cargo.Templates
|
||||||
|
if type(_template) == "table" then
|
||||||
|
_template = _template[1]
|
||||||
|
end
|
||||||
|
if type(_template) == "string" and _template ~= "" then
|
||||||
|
self._cargoByTemplate[_template] = cargo
|
||||||
|
end
|
||||||
if SubCategory and self.usesubcats ~= true then self.usesubcats=true end
|
if SubCategory and self.usesubcats ~= true then self.usesubcats=true end
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
@@ -6855,6 +7083,16 @@ function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock,Sub
|
|||||||
end
|
end
|
||||||
cargo.C130TypeName = C130TypeName
|
cargo.C130TypeName = C130TypeName
|
||||||
table.insert(self.Cargo_Crates,cargo)
|
table.insert(self.Cargo_Crates,cargo)
|
||||||
|
self._crateOrStaticByName = self._crateOrStaticByName or {}
|
||||||
|
self._crateOrStaticByName[cargo.Name] = cargo
|
||||||
|
self._cargoByTemplate = self._cargoByTemplate or {}
|
||||||
|
local _template = cargo.Templates
|
||||||
|
if type(_template) == "table" then
|
||||||
|
_template = _template[1]
|
||||||
|
end
|
||||||
|
if type(_template) == "string" and _template ~= "" then
|
||||||
|
self._cargoByTemplate[_template] = cargo
|
||||||
|
end
|
||||||
if SubCategory and self.usesubcats ~= true then self.usesubcats=true end
|
if SubCategory and self.usesubcats ~= true then self.usesubcats=true end
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
@@ -6895,6 +7133,16 @@ function CTLD:AddCratesCargoNoMove(Name,Templates,Type,NoCrates,PerCrateMass,Sto
|
|||||||
end
|
end
|
||||||
cargo.C130TypeName = C130TypeName
|
cargo.C130TypeName = C130TypeName
|
||||||
table.insert(self.Cargo_Crates,cargo)
|
table.insert(self.Cargo_Crates,cargo)
|
||||||
|
self._crateOrStaticByName = self._crateOrStaticByName or {}
|
||||||
|
self._crateOrStaticByName[cargo.Name] = cargo
|
||||||
|
self._cargoByTemplate = self._cargoByTemplate or {}
|
||||||
|
local _template = cargo.Templates
|
||||||
|
if type(_template) == "table" then
|
||||||
|
_template = _template[1]
|
||||||
|
end
|
||||||
|
if type(_template) == "string" and _template ~= "" then
|
||||||
|
self._cargoByTemplate[_template] = cargo
|
||||||
|
end
|
||||||
self.templateToCargoName = self.templateToCargoName or {}
|
self.templateToCargoName = self.templateToCargoName or {}
|
||||||
if type(Templates)=="table" then
|
if type(Templates)=="table" then
|
||||||
for _,t in pairs(Templates) do self.templateToCargoName[t] = Name end
|
for _,t in pairs(Templates) do self.templateToCargoName[t] = Name end
|
||||||
@@ -6915,11 +7163,13 @@ end
|
|||||||
-- @param #string SubCategory Name of sub-category (optional).
|
-- @param #string SubCategory Name of sub-category (optional).
|
||||||
-- @param #boolean DontShowInMenu (optional) If set to "true" this won't show up in the menu.
|
-- @param #boolean DontShowInMenu (optional) If set to "true" this won't show up in the menu.
|
||||||
-- @param Core.Zone#ZONE Location (optional) If set, the cargo item is **only** available here. Can be a #ZONE object or the name of a zone as #string.
|
-- @param Core.Zone#ZONE Location (optional) If set, the cargo item is **only** available here. Can be a #ZONE object or the name of a zone as #string.
|
||||||
|
-- @param #string UnitTypes Unit type names (optional). If set, only these unit types can pick up the cargo, e.g. "UH-1H" or {"UH-1H","OH58D"}.
|
||||||
|
-- @param #string DisplayName Display name shown in menus/messages (optional). Falls back to `Name`.
|
||||||
-- @return #CTLD_CARGO CargoObject
|
-- @return #CTLD_CARGO CargoObject
|
||||||
function CTLD:AddStaticsCargo(Name,Mass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes)
|
function CTLD:AddStaticsCargo(Name,Mass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes,DisplayName)
|
||||||
self:T(self.lid .. " AddStaticsCargo")
|
self:T(self.lid .. " AddStaticsCargo")
|
||||||
self.CargoCounter = self.CargoCounter + 1
|
self.CargoCounter = self.CargoCounter + 1
|
||||||
local type = CTLD_CARGO.Enum.STATIC
|
local cargotype = CTLD_CARGO.Enum.STATIC
|
||||||
local template = STATIC:FindByName(Name,true):GetTypeName()
|
local template = STATIC:FindByName(Name,true):GetTypeName()
|
||||||
local unittemplate = _DATABASE:GetStaticUnitTemplate(Name)
|
local unittemplate = _DATABASE:GetStaticUnitTemplate(Name)
|
||||||
local ResourceMap = nil
|
local ResourceMap = nil
|
||||||
@@ -6927,12 +7177,69 @@ function CTLD:AddStaticsCargo(Name,Mass,Stock,SubCategory,DontShowInMenu,Locatio
|
|||||||
ResourceMap = UTILS.DeepCopy(unittemplate.resourcePayload)
|
ResourceMap = UTILS.DeepCopy(unittemplate.resourcePayload)
|
||||||
end
|
end
|
||||||
-- Crates are not directly loadable
|
-- Crates are not directly loadable
|
||||||
local cargo = CTLD_CARGO:New(self.CargoCounter,Name,template,type,false,false,1,nil,nil,Mass,Stock,SubCategory,DontShowInMenu,Location)
|
local cargo = CTLD_CARGO:New(self.CargoCounter,Name,template,cargotype,false,false,1,nil,nil,Mass,Stock,SubCategory,DontShowInMenu,Location)
|
||||||
if UnitTypes then
|
if UnitTypes then
|
||||||
cargo:AddUnitTypeName(UnitTypes)
|
cargo:AddUnitTypeName(UnitTypes)
|
||||||
end
|
end
|
||||||
|
cargo:SetDisplayName(DisplayName or Name)
|
||||||
cargo:SetStaticResourceMap(ResourceMap)
|
cargo:SetStaticResourceMap(ResourceMap)
|
||||||
table.insert(self.Cargo_Statics,cargo)
|
table.insert(self.Cargo_Statics,cargo)
|
||||||
|
self._crateOrStaticByName = self._crateOrStaticByName or {}
|
||||||
|
self._crateOrStaticByName[cargo.Name] = cargo
|
||||||
|
self._cargoByTemplate = self._cargoByTemplate or {}
|
||||||
|
local _template = cargo.Templates
|
||||||
|
if type(_template) == "table" then
|
||||||
|
_template = _template[1]
|
||||||
|
end
|
||||||
|
if type(_template) == "string" and _template ~= "" then
|
||||||
|
self._cargoByTemplate[_template] = cargo
|
||||||
|
end
|
||||||
|
if SubCategory and self.usesubcats ~= true then self.usesubcats=true end
|
||||||
|
return cargo
|
||||||
|
end
|
||||||
|
|
||||||
|
--- User function - Add *generic* static-type loadable as cargo from a static **type name**.
|
||||||
|
-- This variant does **not** require a matching mission-editor static template.
|
||||||
|
-- @param #CTLD self
|
||||||
|
-- @param #string Name Unique cargo identifier for this type.
|
||||||
|
-- @param #string TypeName Static type name to spawn, e.g. "ammo_cargo", "iso_container_small".
|
||||||
|
-- @param #number Mass Mass in kg of each static in kg, e.g. 100.
|
||||||
|
-- @param #number Stock Number of groups in stock. Nil for unlimited.
|
||||||
|
-- @param #string SubCategory Name of sub-category (optional).
|
||||||
|
-- @param #boolean DontShowInMenu (optional) If set to "true" this won't show up in the menu.
|
||||||
|
-- @param Core.Zone#ZONE Location (optional) If set, the cargo item is **only** available here. Can be a #ZONE object or the name of a zone as #string.
|
||||||
|
-- @param #string UnitTypes Unit type names (optional). If set, only these unit types can pick up the cargo, e.g. "UH-1H" or {"UH-1H","OH58D"}.
|
||||||
|
-- @param #string Category Static category name (optional). Default is "Cargos".
|
||||||
|
-- @param #string ShapeName Static shape name (optional). If set, force a specific shape, e.g. "iso_container_small_cargo".
|
||||||
|
-- @param #table ResourceMap Resource map payload (optional) for static cargo.
|
||||||
|
-- @param #string DisplayName Display name shown in menus/messages (optional). Falls back to `Name`.
|
||||||
|
-- @return #CTLD_CARGO CargoObject
|
||||||
|
function CTLD:AddStaticsCargoFromType(Name,TypeName,Mass,Stock,SubCategory,DontShowInMenu,Location,UnitTypes,Category,ShapeName,ResourceMap,DisplayName)
|
||||||
|
self:T(self.lid .. " AddStaticsCargoFromType")
|
||||||
|
self.CargoCounter = self.CargoCounter + 1
|
||||||
|
local cargotype = CTLD_CARGO.Enum.STATIC
|
||||||
|
local template = TypeName or self.basetype or "container_cargo"
|
||||||
|
local cargo = CTLD_CARGO:New(self.CargoCounter,Name,template,cargotype,false,false,1,nil,nil,Mass,Stock,SubCategory,DontShowInMenu,Location)
|
||||||
|
if UnitTypes then
|
||||||
|
cargo:AddUnitTypeName(UnitTypes)
|
||||||
|
end
|
||||||
|
cargo:SetStaticTypeAndShape(Category or "Cargos", template, ShapeName)
|
||||||
|
cargo:SetDisplayName(DisplayName or Name)
|
||||||
|
if ResourceMap then
|
||||||
|
ResourceMap = UTILS.DeepCopy(ResourceMap)
|
||||||
|
end
|
||||||
|
cargo:SetStaticResourceMap(ResourceMap)
|
||||||
|
table.insert(self.Cargo_Statics,cargo)
|
||||||
|
self._crateOrStaticByName = self._crateOrStaticByName or {}
|
||||||
|
self._crateOrStaticByName[cargo.Name] = cargo
|
||||||
|
self._cargoByTemplate = self._cargoByTemplate or {}
|
||||||
|
local _template = cargo.Templates
|
||||||
|
if type(_template) == "table" then
|
||||||
|
_template = _template[1]
|
||||||
|
end
|
||||||
|
if type(_template) == "string" and _template ~= "" then
|
||||||
|
self._cargoByTemplate[_template] = cargo
|
||||||
|
end
|
||||||
if SubCategory and self.usesubcats ~= true then self.usesubcats=true end
|
if SubCategory and self.usesubcats ~= true then self.usesubcats=true end
|
||||||
return cargo
|
return cargo
|
||||||
end
|
end
|
||||||
@@ -6941,11 +7248,12 @@ end
|
|||||||
-- @param #CTLD self
|
-- @param #CTLD self
|
||||||
-- @param #string Name Unique Unit(!) name of this type of cargo as set in the mission editor (not: GROUP name!), e.g. "Ammunition-1".
|
-- @param #string Name Unique Unit(!) name of this type of cargo as set in the mission editor (not: GROUP name!), e.g. "Ammunition-1".
|
||||||
-- @param #number Mass Mass in kg of each static in kg, e.g. 100.
|
-- @param #number Mass Mass in kg of each static in kg, e.g. 100.
|
||||||
|
-- @param #string DisplayName Display name shown in menus/messages (optional). Falls back to `Name`.
|
||||||
-- @return #CTLD_CARGO Cargo object
|
-- @return #CTLD_CARGO Cargo object
|
||||||
function CTLD:GetStaticsCargoFromTemplate(Name,Mass)
|
function CTLD:GetStaticsCargoFromTemplate(Name,Mass,DisplayName)
|
||||||
self:T(self.lid .. " GetStaticsCargoFromTemplate")
|
self:T(self.lid .. " GetStaticsCargoFromTemplate")
|
||||||
self.CargoCounter = self.CargoCounter + 1
|
self.CargoCounter = self.CargoCounter + 1
|
||||||
local type = CTLD_CARGO.Enum.STATIC
|
local cargotype = CTLD_CARGO.Enum.STATIC
|
||||||
local template = STATIC:FindByName(Name,true):GetTypeName()
|
local template = STATIC:FindByName(Name,true):GetTypeName()
|
||||||
local unittemplate = _DATABASE:GetStaticUnitTemplate(Name)
|
local unittemplate = _DATABASE:GetStaticUnitTemplate(Name)
|
||||||
local ResourceMap = nil
|
local ResourceMap = nil
|
||||||
@@ -6953,12 +7261,38 @@ function CTLD:GetStaticsCargoFromTemplate(Name,Mass)
|
|||||||
ResourceMap = UTILS.DeepCopy(unittemplate.resourcePayload)
|
ResourceMap = UTILS.DeepCopy(unittemplate.resourcePayload)
|
||||||
end
|
end
|
||||||
-- Crates are not directly loadable
|
-- Crates are not directly loadable
|
||||||
local cargo = CTLD_CARGO:New(self.CargoCounter,Name,template,type,false,false,1,nil,nil,Mass,1)
|
local cargo = CTLD_CARGO:New(self.CargoCounter,Name,template,cargotype,false,false,1,nil,nil,Mass,1)
|
||||||
|
cargo:SetDisplayName(DisplayName or Name)
|
||||||
cargo:SetStaticResourceMap(ResourceMap)
|
cargo:SetStaticResourceMap(ResourceMap)
|
||||||
--table.insert(self.Cargo_Statics,cargo)
|
--table.insert(self.Cargo_Statics,cargo)
|
||||||
return cargo
|
return cargo
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- User function - Get a *generic* static-type loadable cargo from a static **type name**.
|
||||||
|
-- @param #CTLD self
|
||||||
|
-- @param #string Name Unique name of this type of cargo.
|
||||||
|
-- @param #string TypeName Static type name, e.g. "ammo_cargo", "iso_container_small".
|
||||||
|
-- @param #number Mass Mass in kg of each static in kg, e.g. 100.
|
||||||
|
-- @param #string Category Static category name (optional). Default is "Cargos".
|
||||||
|
-- @param #string ShapeName Static shape name (optional).
|
||||||
|
-- @param #table ResourceMap Resource map payload (optional) for static cargo.
|
||||||
|
-- @param #string DisplayName Display name shown in menus/messages (optional). Falls back to `Name`.
|
||||||
|
-- @return #CTLD_CARGO Cargo object
|
||||||
|
function CTLD:GetStaticsCargoFromType(Name,TypeName,Mass,Category,ShapeName,ResourceMap,DisplayName)
|
||||||
|
self:T(self.lid .. " GetStaticsCargoFromType")
|
||||||
|
self.CargoCounter = self.CargoCounter + 1
|
||||||
|
local cargotype = CTLD_CARGO.Enum.STATIC
|
||||||
|
local template = TypeName or self.basetype or "container_cargo"
|
||||||
|
local cargo = CTLD_CARGO:New(self.CargoCounter,Name,template,cargotype,false,false,1,nil,nil,Mass,1)
|
||||||
|
cargo:SetStaticTypeAndShape(Category or "Cargos", template, ShapeName)
|
||||||
|
cargo:SetDisplayName(DisplayName or Name)
|
||||||
|
if ResourceMap then
|
||||||
|
ResourceMap = UTILS.DeepCopy(ResourceMap)
|
||||||
|
end
|
||||||
|
cargo:SetStaticResourceMap(ResourceMap)
|
||||||
|
return cargo
|
||||||
|
end
|
||||||
|
|
||||||
--- User function - Add *generic* repair crates loadable as cargo. This type will create crates that need to be loaded, moved, dropped and built.
|
--- User function - Add *generic* repair crates loadable as cargo. This type will create crates that need to be loaded, moved, dropped and built.
|
||||||
-- @param #CTLD self
|
-- @param #CTLD self
|
||||||
-- @param #string Name Unique name of this type of cargo. E.g. "Humvee".
|
-- @param #string Name Unique name of this type of cargo. E.g. "Humvee".
|
||||||
@@ -6992,6 +7326,16 @@ function CTLD:AddCratesRepair(Name,Template,Type,NoCrates, PerCrateMass,Stock,Su
|
|||||||
cargo:SetStaticTypeAndShape(Category,TypeName,ShapeName)
|
cargo:SetStaticTypeAndShape(Category,TypeName,ShapeName)
|
||||||
end
|
end
|
||||||
table.insert(self.Cargo_Crates,cargo)
|
table.insert(self.Cargo_Crates,cargo)
|
||||||
|
self._crateOrStaticByName = self._crateOrStaticByName or {}
|
||||||
|
self._crateOrStaticByName[cargo.Name] = cargo
|
||||||
|
self._cargoByTemplate = self._cargoByTemplate or {}
|
||||||
|
local _template = cargo.Templates
|
||||||
|
if type(_template) == "table" then
|
||||||
|
_template = _template[1]
|
||||||
|
end
|
||||||
|
if type(_template) == "string" and _template ~= "" then
|
||||||
|
self._cargoByTemplate[_template] = cargo
|
||||||
|
end
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -8429,11 +8773,17 @@ end
|
|||||||
template = string.gsub(GroupName,"#(%d+)$","")
|
template = string.gsub(GroupName,"#(%d+)$","")
|
||||||
end
|
end
|
||||||
template = string.gsub(template,"-(%d+)$","")
|
template = string.gsub(template,"-(%d+)$","")
|
||||||
|
self._cargoByTemplate = self._cargoByTemplate or {}
|
||||||
|
local cached = self._cargoByTemplate[template]
|
||||||
|
if cached and cached.CargoType ~= CTLD_CARGO.Enum.REPAIR then
|
||||||
|
return cached
|
||||||
|
end
|
||||||
for k,v in pairs(self.Cargo_Troops) do
|
for k,v in pairs(self.Cargo_Troops) do
|
||||||
local comparison = ""
|
local comparison = ""
|
||||||
if type(v.Templates) == "string" then comparison = v.Templates else comparison = v.Templates[1] end
|
if type(v.Templates) == "string" then comparison = v.Templates else comparison = v.Templates[1] end
|
||||||
if comparison == template then
|
if comparison == template then
|
||||||
Cargotype = v
|
Cargotype = v
|
||||||
|
self._cargoByTemplate[template] = v
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -8443,6 +8793,7 @@ end
|
|||||||
if type(v.Templates) == "string" then comparison = v.Templates else comparison = v.Templates[1] end
|
if type(v.Templates) == "string" then comparison = v.Templates else comparison = v.Templates[1] end
|
||||||
if comparison == template and v.CargoType ~= CTLD_CARGO.Enum.REPAIR then
|
if comparison == template and v.CargoType ~= CTLD_CARGO.Enum.REPAIR then
|
||||||
Cargotype = v
|
Cargotype = v
|
||||||
|
self._cargoByTemplate[template] = v
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -8867,14 +9218,13 @@ end
|
|||||||
-- @return #CTLD self
|
-- @return #CTLD self
|
||||||
function CTLD:onafterStatus(From, Event, To)
|
function CTLD:onafterStatus(From, Event, To)
|
||||||
self:T({From, Event, To})
|
self:T({From, Event, To})
|
||||||
-- gather some stats
|
if self.debug or self.verbose > 0 then
|
||||||
-- pilots
|
-- gather stats only when logging is enabled
|
||||||
local pilots = 0
|
local pilots = 0
|
||||||
for _,_pilot in pairs (self.CtldUnits) do
|
for _,_pilot in pairs (self.CtldUnits) do
|
||||||
pilots = pilots + 1
|
pilots = pilots + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
-- spawned cargo boxes curr in field
|
|
||||||
local boxes = 0
|
local boxes = 0
|
||||||
for _,_pilot in pairs (self.Spawned_Cargo) do
|
for _,_pilot in pairs (self.Spawned_Cargo) do
|
||||||
boxes = boxes + 1
|
boxes = boxes + 1
|
||||||
@@ -8882,8 +9232,6 @@ end
|
|||||||
|
|
||||||
local cc = self.CargoCounter
|
local cc = self.CargoCounter
|
||||||
local tc = self.TroopCounter
|
local tc = self.TroopCounter
|
||||||
|
|
||||||
if self.debug or self.verbose > 0 then
|
|
||||||
local text = string.format("%s Pilots %d | Live Crates %d |\nCargo Counter %d | Troop Counter %d", self.lid, pilots, boxes, cc, tc)
|
local text = string.format("%s Pilots %d | Live Crates %d |\nCargo Counter %d | Troop Counter %d", self.lid, pilots, boxes, cc, tc)
|
||||||
local m = MESSAGE:New(text,10,"CTLD"):ToAll()
|
local m = MESSAGE:New(text,10,"CTLD"):ToAll()
|
||||||
if self.verbose > 0 then
|
if self.verbose > 0 then
|
||||||
|
|||||||
@@ -50,8 +50,8 @@
|
|||||||
-- @field #string ConfigFilePath Path to the standard config file.
|
-- @field #string ConfigFilePath Path to the standard config file.
|
||||||
-- @field #boolean ConfigLoaded If `true` if config file was loaded.
|
-- @field #boolean ConfigLoaded If `true` if config file was loaded.
|
||||||
-- @field #table poptions Provider options. Each element is a data structure of type `MSRS.ProvierOptions`.
|
-- @field #table poptions Provider options. Each element is a data structure of type `MSRS.ProvierOptions`.
|
||||||
-- @field #string provider Provider of TTS (win, gcloud, azure, amazon).
|
-- @field #string provider Provider of TTS (win, gcloud, azure, amazon, ...).
|
||||||
-- @field #string backend Backend used as interface to SRS (MSRS.Backend.SRSEXE or MSRS.Backend.GRPC).
|
-- @field #string backend Backend used as interface to SRS (MSRS.Backend.SRSEXE, MSRS.Backend.HOUND or MSRS.Backend.GRPC).
|
||||||
-- @field #boolean UsePowerShell Use PowerShell to execute the command and not cmd.exe
|
-- @field #boolean UsePowerShell Use PowerShell to execute the command and not cmd.exe
|
||||||
-- @extends Core.Base#BASE
|
-- @extends Core.Base#BASE
|
||||||
|
|
||||||
@@ -67,7 +67,8 @@
|
|||||||
--
|
--
|
||||||
-- * This script needs SRS version >= 1.9.6
|
-- * This script needs SRS version >= 1.9.6
|
||||||
-- * You need to de-sanitize os, io and lfs in the missionscripting.lua
|
-- * You need to de-sanitize os, io and lfs in the missionscripting.lua
|
||||||
-- * Optional: DCS-gRPC as backend to communicate with SRS (vide infra)
|
-- * Optional: DCS-gRPC as backend to communicate with SRS (via infra)
|
||||||
|
-- * Optional: HOUNDTTS as backend to communicate with SRS (via infra)
|
||||||
--
|
--
|
||||||
-- ## Knwon Issues
|
-- ## Knwon Issues
|
||||||
--
|
--
|
||||||
@@ -77,7 +78,7 @@
|
|||||||
-- expecially in VR but unavoidable (if you have a solution, please feel free to share!).
|
-- expecially in VR but unavoidable (if you have a solution, please feel free to share!).
|
||||||
--
|
--
|
||||||
-- NOTE that this is not an issue if the mission is running on a server.
|
-- NOTE that this is not an issue if the mission is running on a server.
|
||||||
-- Also NOTE that using DCS-gRPC as backend will avoid the pop-up window.
|
-- Also NOTE that using DCS-gRPC and Hound-TTS as backend create no pop-up window.
|
||||||
--
|
--
|
||||||
-- # Play Sound Files
|
-- # Play Sound Files
|
||||||
--
|
--
|
||||||
@@ -188,14 +189,31 @@
|
|||||||
-- The default interface to SRS is via calling the 'DCS-SR-ExternalAudio.exe'. As noted above, this has the unavoidable drawback that a pop-up briefly appears
|
-- The default interface to SRS is via calling the 'DCS-SR-ExternalAudio.exe'. As noted above, this has the unavoidable drawback that a pop-up briefly appears
|
||||||
-- and DCS might be put out of focus.
|
-- and DCS might be put out of focus.
|
||||||
--
|
--
|
||||||
|
-- ## Hound TTS as an alternative to 'DCS-SR-ExternalAudio.exe' for TTS
|
||||||
|
--
|
||||||
|
-- An alternative interface to SRS is [Hound-TTS](https://github.com/uriba107/HoundTTS/releases). This does not call an exe file and therefore avoids the annoying pop-up window.
|
||||||
|
-- In addition to Windows and Google cloud, it also offers Piper local voice creation and others as providers for TTS.
|
||||||
|
--
|
||||||
|
-- Use @{#MSRS.SetDefaultBackendHound} to enable [Hound-TTS](https://github.com/uriba107/HoundTTS/releases) as an alternate backend.
|
||||||
|
-- This can be useful if the popup should be avoided or to use Piper or others for TTS. Please note, only text-to-speech is supported and it it cannot be used to transmit audio files.
|
||||||
|
--
|
||||||
|
-- Hound TTS must be installed and configured per the [Hound TTS](https://github.com/uriba107/HoundTTS#installation) prior to use.
|
||||||
|
-- If a cloud TTS provider is being used, the API key(s) must be set as per the documentation.
|
||||||
|
-- Hound TTS can be used both with DCS dedicated server and regular DCS installations.
|
||||||
|
--
|
||||||
|
-- To use the default local Windows TTS with Hound TTS, Windows 2019 Server (or newer) or Windows 10/11 are required. Voices for non-local languages and dialects may need to
|
||||||
|
-- be explicitly installed.
|
||||||
|
--
|
||||||
|
-- To set the MSRS class to use the Hound TTS backend for all future instances, call the function `MSRS.SetDefaultBackendHound()`.
|
||||||
|
--
|
||||||
-- ## DCS-gRPC as an alternative to 'DCS-SR-ExternalAudio.exe' for TTS
|
-- ## DCS-gRPC as an alternative to 'DCS-SR-ExternalAudio.exe' for TTS
|
||||||
--
|
--
|
||||||
-- Another interface to SRS is [DCS-gRPC](https://github.com/DCS-gRPC/rust-server). This does not call an exe file and therefore avoids the annoying pop-up window.
|
-- Another interface to SRS is [DCS-gRPC](https://github.com/DCS-gRPC/rust-server). This does not call an exe file and therefore avoids the annoying pop-up window.
|
||||||
-- In addition to Windows and Google cloud, it also offers Microsoft Azure and Amazon Web Service as providers for TTS.
|
-- In addition to Windows and Google cloud, it also offers Microsoft Azure and Amazon Web Service as providers for TTS.
|
||||||
--
|
--
|
||||||
-- Use @{#MSRS.SetDefaultBackendGRPC} to enable [DCS-gRPC](https://github.com/DCS-gRPC/rust-server) as an alternate backend for transmitting text-to-speech over SRS.
|
-- Use @{#MSRS.SetDefaultBackendGRPC} to enable [DCS-gRPC](https://github.com/DCS-gRPC/rust-server) as an alternate backend.
|
||||||
-- This can be useful if 'DCS-SR-ExternalAudio.exe' cannot be used in the environment or to use Azure or AWS clouds for TTS. Note that DCS-gRPC does not (yet?) support
|
-- This can be useful if 'DCS-SR-ExternalAudio.exe' cannot be used in the environment or to use Azure or AWS clouds for TTS. Note that DCS-gRPC does not support
|
||||||
-- all of the features and options available with 'DCS-SR-ExternalAudio.exe'. Of note, only text-to-speech is supported and it it cannot be used to transmit audio files.
|
-- all of the features and options available with 'DCS-SR-ExternalAudio.exe'. Also note, only text-to-speech is supported and it it cannot be used to transmit audio files.
|
||||||
--
|
--
|
||||||
-- DCS-gRPC must be installed and configured per the [DCS-gRPC documentation](https://github.com/DCS-gRPC/rust-server) and already running via either the 'autostart' mechanism
|
-- DCS-gRPC must be installed and configured per the [DCS-gRPC documentation](https://github.com/DCS-gRPC/rust-server) and already running via either the 'autostart' mechanism
|
||||||
-- or a Lua call to 'GRPC.load()' prior to use of the alternate DCS-gRPC backend. If a cloud TTS provider is being used, the API key must be set via the 'Config\dcs-grpc.lua'
|
-- or a Lua call to 'GRPC.load()' prior to use of the alternate DCS-gRPC backend. If a cloud TTS provider is being used, the API key must be set via the 'Config\dcs-grpc.lua'
|
||||||
@@ -212,20 +230,20 @@
|
|||||||
-- Basic Play Text-To-Speech example using alternate DCS-gRPC backend (DCS-gRPC not previously started):
|
-- Basic Play Text-To-Speech example using alternate DCS-gRPC backend (DCS-gRPC not previously started):
|
||||||
--
|
--
|
||||||
-- -- Start DCS-gRPC
|
-- -- Start DCS-gRPC
|
||||||
-- GRPC.load()
|
-- GRPC.load() -- not needed if you set gRPC to auto-start
|
||||||
-- -- Select the alternate DCS-gRPC backend for new MSRS instances
|
-- -- Select the alternate DCS-gRPC backend for new MSRS instances
|
||||||
-- MSRS.SetDefaultBackendGRPC()
|
-- MSRS.SetDefaultBackendGRPC()
|
||||||
-- -- Create a SOUNDTEXT object.
|
-- -- Create a SOUNDTEXT object.
|
||||||
-- local text=SOUNDTEXT:New("All Enemies destroyed")
|
-- local text=SOUNDTEXT:New("All Enemies destroyed")
|
||||||
-- -- MOOSE SRS
|
-- -- MOOSE SRS
|
||||||
-- local msrs=MSRS:New('', 305.0)
|
-- local msrs=MSRS:New('ExampleInstance', 305.0)
|
||||||
-- -- Text-to speech with default voice after 30 seconds.
|
-- -- Text-to speech with default voice after 30 seconds.
|
||||||
-- msrs:PlaySoundText(text, 30)
|
-- msrs:PlaySoundText(text, 30)
|
||||||
--
|
--
|
||||||
-- Basic example of using another class (ATIS) with SRS and the DCS-gRPC backend (DCS-gRPC not previously started):
|
-- Basic example of using another class (ATIS) with SRS and the DCS-gRPC backend (DCS-gRPC not previously started):
|
||||||
--
|
--
|
||||||
-- -- Start DCS-gRPC
|
-- -- Start DCS-gRPC
|
||||||
-- GRPC.load()
|
-- GRPC.load() -- not needed if you set gRPC to auto-start
|
||||||
-- -- Select the alternate DCS-gRPC backend for new MSRS instances
|
-- -- Select the alternate DCS-gRPC backend for new MSRS instances
|
||||||
-- MSRS.SetDefaultBackendGRPC()
|
-- MSRS.SetDefaultBackendGRPC()
|
||||||
-- -- Create new ATIS as usual
|
-- -- Create new ATIS as usual
|
||||||
@@ -262,7 +280,7 @@ MSRS = {
|
|||||||
|
|
||||||
--- MSRS class version.
|
--- MSRS class version.
|
||||||
-- @field #string version
|
-- @field #string version
|
||||||
MSRS.version="0.3.3"
|
MSRS.version="0.3.4"
|
||||||
|
|
||||||
--- Voices
|
--- Voices
|
||||||
-- @type MSRS.Voices
|
-- @type MSRS.Voices
|
||||||
@@ -699,9 +717,11 @@ MSRS.Voices = {
|
|||||||
-- @type MSRS.Backend
|
-- @type MSRS.Backend
|
||||||
-- @field #string SRSEXE Use `DCS-SR-ExternalAudio.exe`.
|
-- @field #string SRSEXE Use `DCS-SR-ExternalAudio.exe`.
|
||||||
-- @field #string GRPC Use DCS-gRPC.
|
-- @field #string GRPC Use DCS-gRPC.
|
||||||
|
-- @field #string GRPC Use Hound-TTS.
|
||||||
MSRS.Backend = {
|
MSRS.Backend = {
|
||||||
SRSEXE = "srsexe",
|
SRSEXE = "srsexe",
|
||||||
GRPC = "grpc",
|
GRPC = "grpc",
|
||||||
|
HOUND = "hound",
|
||||||
}
|
}
|
||||||
|
|
||||||
--- Text-to-speech providers. These are compatible with the DCS-gRPC conventions.
|
--- Text-to-speech providers. These are compatible with the DCS-gRPC conventions.
|
||||||
@@ -710,11 +730,13 @@ MSRS.Backend = {
|
|||||||
-- @field #string GOOGLE Google (`gcloud`).
|
-- @field #string GOOGLE Google (`gcloud`).
|
||||||
-- @field #string AZURE Microsoft Azure (`azure`). Only possible with DCS-gRPC backend.
|
-- @field #string AZURE Microsoft Azure (`azure`). Only possible with DCS-gRPC backend.
|
||||||
-- @field #string AMAZON Amazon Web Service (`aws`). Only possible with DCS-gRPC backend.
|
-- @field #string AMAZON Amazon Web Service (`aws`). Only possible with DCS-gRPC backend.
|
||||||
|
-- @field #string PIPER Piper local voice service. Only possible with Hound-TTS backend.
|
||||||
MSRS.Provider = {
|
MSRS.Provider = {
|
||||||
WINDOWS = "win",
|
WINDOWS = "win",
|
||||||
GOOGLE = "gcloud",
|
GOOGLE = "gcloud",
|
||||||
AZURE = "azure",
|
AZURE = "azure",
|
||||||
AMAZON = "aws",
|
AMAZON = "aws",
|
||||||
|
PIPER = "piper",
|
||||||
}
|
}
|
||||||
|
|
||||||
--- Function for UUID.
|
--- Function for UUID.
|
||||||
@@ -760,6 +782,7 @@ end
|
|||||||
-- DONE: Add google.
|
-- DONE: Add google.
|
||||||
-- DONE: Add gRPC google options
|
-- DONE: Add gRPC google options
|
||||||
-- DONE: Add loading default config file
|
-- DONE: Add loading default config file
|
||||||
|
-- DONE: Add Hound TTS
|
||||||
|
|
||||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
-- Constructor
|
-- Constructor
|
||||||
@@ -874,6 +897,16 @@ function MSRS:SetBackendGRPC()
|
|||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Set Hound-TTS as backend to communicate with SRS.
|
||||||
|
-- @param #MSRS self
|
||||||
|
-- @return #MSRS self
|
||||||
|
function MSRS:SetBackendHound()
|
||||||
|
self:F()
|
||||||
|
self:SetBackend(MSRS.Backend.HOUND)
|
||||||
|
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
--- Set `DCS-SR-ExternalAudio.exe` as backend to communicate with SRS.
|
--- Set `DCS-SR-ExternalAudio.exe` as backend to communicate with SRS.
|
||||||
-- @param #MSRS self
|
-- @param #MSRS self
|
||||||
-- @return #MSRS self
|
-- @return #MSRS self
|
||||||
@@ -896,6 +929,12 @@ function MSRS.SetDefaultBackendGRPC()
|
|||||||
MSRS.backend=MSRS.Backend.GRPC
|
MSRS.backend=MSRS.Backend.GRPC
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Set Hound-TTS to be the default backend.
|
||||||
|
-- @param #MSRS self
|
||||||
|
function MSRS.SetDefaultBackendHound()
|
||||||
|
MSRS.backend=MSRS.Backend.HOUND
|
||||||
|
end
|
||||||
|
|
||||||
--- Get currently set backend.
|
--- Get currently set backend.
|
||||||
-- @param #MSRS self
|
-- @param #MSRS self
|
||||||
-- @return #string Backend.
|
-- @return #string Backend.
|
||||||
@@ -1147,6 +1186,16 @@ function MSRS:SetVoiceGoogle(Voice)
|
|||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Set to use a specific voice if Piper is used as provider (only Hound-TTS backend). Note that this will override any gender and culture settings.
|
||||||
|
-- @param #MSRS self
|
||||||
|
-- @param #string Voice [Piper Voices](https://rhasspy.github.io/piper-samples/). Default `"en_US-ryan-low"`.
|
||||||
|
-- @return #MSRS self
|
||||||
|
function MSRS:SetVoicePiper(Voice)
|
||||||
|
self:F( {Voice=Voice} )
|
||||||
|
self:SetVoiceProvider(Voice or "en_US-ryan-low", MSRS.Provider.PIPER)
|
||||||
|
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
--- Set to use a specific voice if Microsoft Azure is use as provider (only DCS-gRPC backend). Note that this will override any gender and culture settings.
|
--- Set to use a specific voice if Microsoft Azure is use as provider (only DCS-gRPC backend). Note that this will override any gender and culture settings.
|
||||||
-- @param #MSRS self
|
-- @param #MSRS self
|
||||||
@@ -1241,9 +1290,10 @@ end
|
|||||||
-- - `MSRS.Provider.WINDOWS`: Microsoft Windows (default)
|
-- - `MSRS.Provider.WINDOWS`: Microsoft Windows (default)
|
||||||
-- - `MSRS.Provider.GOOGLE`: Google Cloud
|
-- - `MSRS.Provider.GOOGLE`: Google Cloud
|
||||||
-- - `MSRS.Provider.AZURE`: Microsoft Azure (only with DCS-gRPC backend)
|
-- - `MSRS.Provider.AZURE`: Microsoft Azure (only with DCS-gRPC backend)
|
||||||
-- - `MSRS.Provier.AMAZON`: Amazon Web Service (only with DCS-gRPC backend)
|
-- - `MSRS.Provider.AMAZON`: Amazon Web Service (only with DCS-gRPC backend)
|
||||||
|
-- - `MSRS.Provider.PIPER`: Piper Voices (only with Hound-TTS backend)
|
||||||
--
|
--
|
||||||
-- Note that all providers except Microsoft Windows need as additonal information the credentials of your account.
|
-- Note that all providers except Microsoft Windows and Piper need as additonal information the credentials of your account.
|
||||||
--
|
--
|
||||||
-- @param #MSRS self
|
-- @param #MSRS self
|
||||||
-- @param #string Provider
|
-- @param #string Provider
|
||||||
@@ -1390,6 +1440,15 @@ function MSRS:SetTTSProviderAmazon()
|
|||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Use Piper to provide text-to-speech. Only supported if used in combination with Hound-TTS as backend.
|
||||||
|
-- @param #MSRS self
|
||||||
|
-- @return #MSRS self
|
||||||
|
function MSRS:SetTTSProviderPiper()
|
||||||
|
self:F()
|
||||||
|
self:SetProvider(MSRS.Provider.PIPER)
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
--- Print SRS help to DCS log file.
|
--- Print SRS help to DCS log file.
|
||||||
-- @param #MSRS self
|
-- @param #MSRS self
|
||||||
@@ -1473,8 +1532,11 @@ function MSRS:PlaySoundText(SoundText, Delay)
|
|||||||
self:ScheduleOnce(Delay, MSRS.PlaySoundText, self, SoundText, 0)
|
self:ScheduleOnce(Delay, MSRS.PlaySoundText, self, SoundText, 0)
|
||||||
else
|
else
|
||||||
|
|
||||||
|
-- TODO Insert HOUND option
|
||||||
if self.backend==MSRS.Backend.GRPC then
|
if self.backend==MSRS.Backend.GRPC then
|
||||||
self:_DCSgRPCtts(SoundText.text, nil, SoundText.gender, SoundText.culture, SoundText.voice, SoundText.volume, SoundText.label, SoundText.coordinate)
|
self:_DCSgRPCtts(SoundText.text, nil, SoundText.gender, SoundText.culture, SoundText.voice, SoundText.volume, SoundText.label, SoundText.coordinate)
|
||||||
|
elseif self.backend == MSRS.Backend.HOUND then
|
||||||
|
self:_HoundTextToSpeech(SoundText.text,nil,nil,SoundText.volume,SoundText.label,self.coalition,SoundText.coordinate,SoundText.Speed,SoundText.gender,SoundText.culture,SoundText.voice)
|
||||||
else
|
else
|
||||||
|
|
||||||
-- Get command.
|
-- Get command.
|
||||||
@@ -1498,8 +1560,9 @@ end
|
|||||||
-- @param #string Text Text message.
|
-- @param #string Text Text message.
|
||||||
-- @param #number Delay Delay in seconds, before the message is played.
|
-- @param #number Delay Delay in seconds, before the message is played.
|
||||||
-- @param Core.Point#COORDINATE Coordinate Coordinate.
|
-- @param Core.Point#COORDINATE Coordinate Coordinate.
|
||||||
|
-- @param #number Speed
|
||||||
-- @return #MSRS self
|
-- @return #MSRS self
|
||||||
function MSRS:PlayText(Text, Delay, Coordinate)
|
function MSRS:PlayText(Text, Delay, Coordinate, Speed)
|
||||||
self:F( {Text, Delay, Coordinate} )
|
self:F( {Text, Delay, Coordinate} )
|
||||||
|
|
||||||
if Delay and Delay>0 then
|
if Delay and Delay>0 then
|
||||||
@@ -1509,8 +1572,10 @@ function MSRS:PlayText(Text, Delay, Coordinate)
|
|||||||
if self.backend==MSRS.Backend.GRPC then
|
if self.backend==MSRS.Backend.GRPC then
|
||||||
self:T(self.lid.."Transmitting")
|
self:T(self.lid.."Transmitting")
|
||||||
self:_DCSgRPCtts(Text, nil, nil , nil, nil, nil, nil, Coordinate)
|
self:_DCSgRPCtts(Text, nil, nil , nil, nil, nil, nil, Coordinate)
|
||||||
|
elseif self.backend==MSRS.Backend.HOUND then
|
||||||
|
self:_HoundTextToSpeech(Text,nil,nil,nil,nil,nil,Coordinate,Speed)
|
||||||
else
|
else
|
||||||
self:PlayTextExt(Text, Delay, nil, nil, nil, nil, nil, nil, nil, Coordinate)
|
self:PlayTextExt(Text, Delay, nil, nil, nil, nil, nil, nil, nil, Coordinate, Speed)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
@@ -1530,12 +1595,13 @@ end
|
|||||||
-- @param #number Volume Volume.
|
-- @param #number Volume Volume.
|
||||||
-- @param #string Label Label.
|
-- @param #string Label Label.
|
||||||
-- @param Core.Point#COORDINATE Coordinate Coordinate.
|
-- @param Core.Point#COORDINATE Coordinate Coordinate.
|
||||||
|
-- @param #number Speed Speed.
|
||||||
-- @return #MSRS self
|
-- @return #MSRS self
|
||||||
function MSRS:PlayTextExt(Text, Delay, Frequencies, Modulations, Gender, Culture, Voice, Volume, Label, Coordinate)
|
function MSRS:PlayTextExt(Text, Delay, Frequencies, Modulations, Gender, Culture, Voice, Volume, Label, Coordinate,Speed)
|
||||||
self:T({Text, Delay, Frequencies, Modulations, Gender, Culture, Voice, Volume, Label, Coordinate} )
|
self:T({Text, Delay, Frequencies, Modulations, Gender, Culture, Voice, Volume, Label, Coordinate, Speed} )
|
||||||
|
|
||||||
if Delay and Delay>0 then
|
if Delay and Delay>0 then
|
||||||
self:ScheduleOnce(Delay, self.PlayTextExt, self, Text, 0, Frequencies, Modulations, Gender, Culture, Voice, Volume, Label, Coordinate)
|
self:ScheduleOnce(Delay, self.PlayTextExt, self, Text, 0, Frequencies, Modulations, Gender, Culture, Voice, Volume, Label, Coordinate, Speed)
|
||||||
else
|
else
|
||||||
|
|
||||||
Frequencies = Frequencies or self:GetFrequencies()
|
Frequencies = Frequencies or self:GetFrequencies()
|
||||||
@@ -1557,6 +1623,13 @@ function MSRS:PlayTextExt(Text, Delay, Frequencies, Modulations, Gender, Culture
|
|||||||
|
|
||||||
self:_DCSgRPCtts(Text, Frequencies, Gender, Culture, Voice, Volume, Label, Coordinate)
|
self:_DCSgRPCtts(Text, Frequencies, Gender, Culture, Voice, Volume, Label, Coordinate)
|
||||||
|
|
||||||
|
elseif self.backend==MSRS.Backend.HOUND then
|
||||||
|
-- BASE:I("MSRS.Backend.HOUND")
|
||||||
|
|
||||||
|
local UseGoogle = (self.provider == MSRS.Provider.GOOGLE) and true or nil
|
||||||
|
|
||||||
|
self:_HoundTextToSpeech(Text,Frequencies,Modulations,Volume,Label,self.coalition,Coordinate,Speed,Gender,Culture,Voice,UseGoogle)
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
@@ -1871,7 +1944,7 @@ end
|
|||||||
function MSRS:_DCSgRPCtts(Text, Frequencies, Gender, Culture, Voice, Volume, Label, Coordinate)
|
function MSRS:_DCSgRPCtts(Text, Frequencies, Gender, Culture, Voice, Volume, Label, Coordinate)
|
||||||
|
|
||||||
-- Debug info.
|
-- Debug info.
|
||||||
self:T("MSRS_BACKEND_DCSGRPC:_DCSgRPCtts()")
|
self:T("MSRS_BACKEND_DCSGRPC:_DCSgRPCtts")
|
||||||
self:T({Text, Frequencies, Gender, Culture, Voice, Volume, Label, Coordinate})
|
self:T({Text, Frequencies, Gender, Culture, Voice, Volume, Label, Coordinate})
|
||||||
|
|
||||||
local options = {} -- #MSRS.GRPCOptions
|
local options = {} -- #MSRS.GRPCOptions
|
||||||
@@ -1881,7 +1954,7 @@ function MSRS:_DCSgRPCtts(Text, Frequencies, Gender, Culture, Voice, Volume, Lab
|
|||||||
-- Get frequenceies.
|
-- Get frequenceies.
|
||||||
Frequencies = UTILS.EnsureTable(Frequencies, true) or self:GetFrequencies()
|
Frequencies = UTILS.EnsureTable(Frequencies, true) or self:GetFrequencies()
|
||||||
|
|
||||||
-- Plain text (not really used.
|
-- Plain text - not really used.
|
||||||
options.plaintext=Text
|
options.plaintext=Text
|
||||||
|
|
||||||
-- Name shows as sender.
|
-- Name shows as sender.
|
||||||
@@ -1938,6 +2011,156 @@ function MSRS:_DCSgRPCtts(Text, Frequencies, Gender, Culture, Voice, Volume, Lab
|
|||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
-- Hound-TTS Backend Functions
|
||||||
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
--- Hound TTS Function Wrapper
|
||||||
|
-- @param #MSRS self
|
||||||
|
-- @param #string Message The text to speak.
|
||||||
|
-- @param #table Frequencies The table of frequencies to use.
|
||||||
|
-- @param #table Modulations The table of modulations to use.
|
||||||
|
-- @param #number Volume The volume to use, defaults to 1.0.
|
||||||
|
-- @param #string Label The label to use, defaults to "MSRS".
|
||||||
|
-- @param #number Coalition The coalition to use.
|
||||||
|
-- @param Core.Point#COORDINATE Point (Optional) The point from which the voice is sent.
|
||||||
|
-- @param #number Speed (Optional) How fast to speak, defaults to 1.0.
|
||||||
|
-- @param #string Gender (Optional) Gender to use.
|
||||||
|
-- @param #string Culture (Optional) Culture to use.
|
||||||
|
-- @param #string Voice (Optional) Voice to use.
|
||||||
|
-- @param #boolean UseGoogle (Optional) If to use Google TTS.
|
||||||
|
-- @return SpeechTime Speech time in seconds.
|
||||||
|
function MSRS:_HoundTextToSpeech(Message,Frequencies,Modulations,Volume,Label,Coalition,Point,Speed,Gender,Culture,Voice,UseGoogle)
|
||||||
|
self:I(self.lid.."_HoundTextToSpeech")
|
||||||
|
|
||||||
|
local ffs = {}
|
||||||
|
for _,_f in pairs(Frequencies or self.frequencies) do
|
||||||
|
table.insert(ffs,string.format("%.1f",_f))
|
||||||
|
end
|
||||||
|
|
||||||
|
local freqs = table.concat(ffs, ",")
|
||||||
|
local modus = table.concat(Modulations or self.modulations, ",")
|
||||||
|
|
||||||
|
local coal=Coalition or self.coalition
|
||||||
|
local gender=Gender or self.gender
|
||||||
|
local voice=Voice or self:GetVoice(self.provider) or self.voice
|
||||||
|
local culture=Culture or self.culture
|
||||||
|
local volume=Volume or self.volume or 1.0
|
||||||
|
local speed=Speed or self.speed or 1.0
|
||||||
|
local label=Label or self.Label or "MSRS"
|
||||||
|
local coordinate=Point or self.coordinate
|
||||||
|
local point = (coordinate ~= nil) and coordinate:GetVec3() or nil
|
||||||
|
local port = self.port or 5002
|
||||||
|
|
||||||
|
-- Replace modulation
|
||||||
|
modus=modus:gsub("0", "AM")
|
||||||
|
modus=modus:gsub("1", "FM")
|
||||||
|
|
||||||
|
self:I({T=Message,F=freqs,M=modus,V=voice,Vx=volume,L=label,C=coal,GGL=tostring(UseGoogle)})
|
||||||
|
|
||||||
|
if (UseGoogle ~= true) and self.provider == MSRS.Provider.GOOGLE then
|
||||||
|
UseGoogle = true
|
||||||
|
end
|
||||||
|
|
||||||
|
local provider = self.provider
|
||||||
|
provider=provider:gsub("gcloud", "google")
|
||||||
|
provider=provider:gsub("win", "sapi")
|
||||||
|
|
||||||
|
local TransmissionP = {
|
||||||
|
freqs = freqs,
|
||||||
|
modulations = modus,
|
||||||
|
coalition = coal,
|
||||||
|
name = label,
|
||||||
|
point = point,
|
||||||
|
volume = volume,
|
||||||
|
port = port,
|
||||||
|
}
|
||||||
|
local ProviderP = {
|
||||||
|
provider = provider,
|
||||||
|
voice = voice,
|
||||||
|
speed = speed,
|
||||||
|
culture = culture,
|
||||||
|
gender = gender,
|
||||||
|
}
|
||||||
|
|
||||||
|
local speechtime = HoundTTS.Transmit(Message, TransmissionP, ProviderP)
|
||||||
|
|
||||||
|
return speechtime
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Hound Transmit Function Wrapper
|
||||||
|
-- @param #MSRS self
|
||||||
|
-- @param #string Message The message to speak
|
||||||
|
-- @param #table Transmission_params Transmission parameter table, see below
|
||||||
|
-- @param #table Provider_params Provider parameter table, see below
|
||||||
|
-- @return SpeechTime Speech time in seconds.
|
||||||
|
-- @usage
|
||||||
|
-- -- #table Transmission_params
|
||||||
|
-- | Field | Type | Default | Description |
|
||||||
|
-- | ----------- | -------- | ------------------- | ---------------------------------------- |
|
||||||
|
-- | transmitter | string | `"srs"` | Transmitter type. Currently only `"srs"` |
|
||||||
|
-- | freqs | string | `"251.0"` | Frequency in MHz, comma-separated |
|
||||||
|
-- | modulations | string | `"AM"` | `AM` or `FM`, comma-separated |
|
||||||
|
-- | coalition | number | `0` | 0=spectator, 1=red, 2=blue |
|
||||||
|
-- | name | string | `"HoundTTS"` | Client name shown in SRS |
|
||||||
|
-- | point | Vec3/nil | `nil` | DCS position for geo-location |
|
||||||
|
-- | volume | number | `1.0` | 0.0 – 1.0 |
|
||||||
|
-- | encrypt | boolean | `false` | Enable SRS encryption |
|
||||||
|
-- | encKey | number | `0` | Encryption key (0–255, must match SRS) |
|
||||||
|
-- | host | string | `HoundTTS.SRS_HOST` | SRS server IP |
|
||||||
|
-- | port | number | `HoundTTS.SRS_PORT` | SRS server port |
|
||||||
|
--
|
||||||
|
-- -- #table Provider_params
|
||||||
|
-- | Field | Type | Default | Description |
|
||||||
|
-- | -------- | ------ | --------------------------- | ---------------------------------------------------------------------------------- |
|
||||||
|
-- | provider | string | `HoundTTS.DEFAULT_PROVIDER` | `"piper"` / `"sapi"` / `"azure"` / `"google"` / `"elevenlabs"` |
|
||||||
|
-- | voice | string | `HoundTTS.DEFAULT_VOICE` | Piper model name, SAPI voice name, Azure/Google voice name, or ElevenLabs voice ID |
|
||||||
|
-- | culture | string | `HoundTTS.DEFAULT_CULTURE` | BCP-47 locale e.g. `"en-US"`, `"en-GB"` (used by SAPI, Azure, Google) |
|
||||||
|
-- | gender | string | `HoundTTS.DEFAULT_GENDER` | `"male"` / `"female"` (used by SAPI, Google) |
|
||||||
|
-- | speed | number | `1.0` | Speech rate (0.5 = half speed, 1.0 = normal, 2.0 = double speed) |
|
||||||
|
--
|
||||||
|
function MSRS:_HoundTransmit(Message, Transmission_params, Provider_params)
|
||||||
|
self:I(self.lid.."_HoundTransmit")
|
||||||
|
self:I({Message,Transmission_params,Provider_params})
|
||||||
|
local speechtime = HoundTTS.Transmit(Message, Transmission_params, Provider_params)
|
||||||
|
return speechtime
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Hound Test Tone function, sends a 2-second 440 Hz sine wave tone directly over SRS, bypassing the TTS engine entirely.
|
||||||
|
-- Use this to verify the SRS connection is working before debugging TTS issues.
|
||||||
|
-- @param MSRS self
|
||||||
|
-- @param #table Frequencies The table of frequencies to use.
|
||||||
|
-- @param #table Modulations The table of modulations to use.
|
||||||
|
-- @param #number Coalition The coalition to use.
|
||||||
|
function MSRS:_HoundTestTone(Frequencies, Modulations, Coalition)
|
||||||
|
self:I(self.lid.."_HoundTestTone")
|
||||||
|
|
||||||
|
local ffs = {}
|
||||||
|
for _,_f in pairs(Frequencies or self.frequencies) do
|
||||||
|
table.insert(ffs,string.format("%.1f",_f))
|
||||||
|
end
|
||||||
|
|
||||||
|
local freqs = table.concat(ffs, ",")
|
||||||
|
local modus = table.concat(Modulations or self.modulations, ",")
|
||||||
|
-- Replace modulation
|
||||||
|
modus=modus:gsub("0", "AM")
|
||||||
|
modus=modus:gsub("1", "FM")
|
||||||
|
local coal=Coalition or self.coalition
|
||||||
|
HoundTTS.TestTone(freqs, modus, coal)
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Hound speech time calculator. Use to determine how long it takes to speak something out.
|
||||||
|
-- @param MSRS self
|
||||||
|
-- @param #string Message The message to measure. Can also be handed as string lenght.
|
||||||
|
-- @param #number Speed The speed to use, defaults to 1.0.
|
||||||
|
-- @param #boolean UseGoogle If to use google. Default: no.
|
||||||
|
function MSRS:_HoundSpeechTime(Message,Speed,UseGoogle)
|
||||||
|
local speed = Speed or 1.0
|
||||||
|
local speechtime = HoundTTS.getSpeechTime(Message, speed, UseGoogle)
|
||||||
|
return speechtime
|
||||||
|
end
|
||||||
|
|
||||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
-- Config File
|
-- Config File
|
||||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
@@ -1957,7 +2180,7 @@ end
|
|||||||
-- MSRS_Config = {
|
-- MSRS_Config = {
|
||||||
-- Path = "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio", -- Path to SRS install directory.
|
-- Path = "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio", -- Path to SRS install directory.
|
||||||
-- Port = 5002, -- Port of SRS server. Default 5002.
|
-- Port = 5002, -- Port of SRS server. Default 5002.
|
||||||
-- Backend = "srsexe", -- Interface to SRS: "srsexe" or "grpc".
|
-- Backend = "srsexe", -- Interface to SRS: "srsexe" or "grpc" or "hound".
|
||||||
-- Frequency = {127, 243}, -- Default frequences. Must be a table 1..n entries!
|
-- Frequency = {127, 243}, -- Default frequences. Must be a table 1..n entries!
|
||||||
-- Modulation = {0,0}, -- Default modulations. Must be a table, 1..n entries, one for each frequency!
|
-- Modulation = {0,0}, -- Default modulations. Must be a table, 1..n entries, one for each frequency!
|
||||||
-- Volume = 1.0, -- Default volume [0,1].
|
-- Volume = 1.0, -- Default volume [0,1].
|
||||||
@@ -1967,7 +2190,7 @@ end
|
|||||||
-- Gender = "male",
|
-- Gender = "male",
|
||||||
-- Voice = "Microsoft Hazel Desktop", -- Voice that is used if no explicit provider voice is specified.
|
-- Voice = "Microsoft Hazel Desktop", -- Voice that is used if no explicit provider voice is specified.
|
||||||
-- Label = "MSRS",
|
-- Label = "MSRS",
|
||||||
-- Provider = "win", --Provider for generating TTS (win, gcloud, azure, aws).
|
-- Provider = "win", --Provider for generating TTS (win, gcloud, azure, aws, piper).
|
||||||
-- -- Windows
|
-- -- Windows
|
||||||
-- win = {
|
-- win = {
|
||||||
-- voice = "Microsoft Hazel Desktop",
|
-- voice = "Microsoft Hazel Desktop",
|
||||||
@@ -2092,6 +2315,11 @@ end
|
|||||||
-- @param #boolean isGoogle We're using Google TTS
|
-- @param #boolean isGoogle We're using Google TTS
|
||||||
function MSRS.getSpeechTime(length,speed,isGoogle)
|
function MSRS.getSpeechTime(length,speed,isGoogle)
|
||||||
|
|
||||||
|
if MSRS.backend == MSRS.Backend.HOUND then
|
||||||
|
local speechtime = HoundTTS.getSpeechTime(length, speed, isGoogle)
|
||||||
|
return speechtime
|
||||||
|
else
|
||||||
|
|
||||||
local maxRateRatio = 3
|
local maxRateRatio = 3
|
||||||
|
|
||||||
speed = speed or 1.0
|
speed = speed or 1.0
|
||||||
@@ -2117,6 +2345,8 @@ function MSRS.getSpeechTime(length,speed,isGoogle)
|
|||||||
end
|
end
|
||||||
|
|
||||||
return length/cps --math.ceil(length/cps)
|
return length/cps --math.ceil(length/cps)
|
||||||
|
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
@@ -2168,6 +2398,7 @@ MSRSQUEUE = {
|
|||||||
-- @field #number volume Volume
|
-- @field #number volume Volume
|
||||||
-- @field #string label Label to be used
|
-- @field #string label Label to be used
|
||||||
-- @field Core.Point#COORDINATE coordinate Coordinate for this transmission
|
-- @field Core.Point#COORDINATE coordinate Coordinate for this transmission
|
||||||
|
-- @field #number speed Speed to be used
|
||||||
|
|
||||||
--- Create a new MSRSQUEUE object for a given radio frequency/modulation.
|
--- Create a new MSRSQUEUE object for a given radio frequency/modulation.
|
||||||
-- @param #MSRSQUEUE self
|
-- @param #MSRSQUEUE self
|
||||||
@@ -2252,9 +2483,10 @@ end
|
|||||||
-- @param #number volume Volume setting
|
-- @param #number volume Volume setting
|
||||||
-- @param #string label Label to be used
|
-- @param #string label Label to be used
|
||||||
-- @param Core.Point#COORDINATE coordinate Coordinate to be used
|
-- @param Core.Point#COORDINATE coordinate Coordinate to be used
|
||||||
|
-- @param #number speed Speed to be used
|
||||||
-- @return #MSRSQUEUE.Transmission Radio transmission table.
|
-- @return #MSRSQUEUE.Transmission Radio transmission table.
|
||||||
function MSRSQUEUE:NewTransmission(text, duration, msrs, tstart, interval, subgroups, subtitle, subduration, frequency, modulation, gender, culture, voice, volume, label,coordinate)
|
function MSRSQUEUE:NewTransmission(text, duration, msrs, tstart, interval, subgroups, subtitle, subduration, frequency, modulation, gender, culture, voice, volume, label,coordinate,speed)
|
||||||
self:T({Text=text, Dur=duration, start=tstart, int=interval, sub=subgroups, subt=subtitle, sudb=subduration, F=frequency, M=modulation, G=gender, C=culture, V=voice, Vol=volume, L=label})
|
self:T({Text=text, Dur=duration, start=tstart, int=interval, sub=subgroups, subt=subtitle, sudb=subduration, F=frequency, M=modulation, G=gender, C=culture, V=voice, Vol=volume, L=label, S=speed})
|
||||||
if self.TransmitOnlyWithPlayers then
|
if self.TransmitOnlyWithPlayers then
|
||||||
if self.PlayerSet and self.PlayerSet:CountAlive() == 0 then
|
if self.PlayerSet and self.PlayerSet:CountAlive() == 0 then
|
||||||
return self
|
return self
|
||||||
@@ -2294,6 +2526,7 @@ function MSRSQUEUE:NewTransmission(text, duration, msrs, tstart, interval, subgr
|
|||||||
transmission.volume = volume or msrs.volume
|
transmission.volume = volume or msrs.volume
|
||||||
transmission.label = label or msrs.Label
|
transmission.label = label or msrs.Label
|
||||||
transmission.coordinate = coordinate or msrs.coordinate
|
transmission.coordinate = coordinate or msrs.coordinate
|
||||||
|
transmission.speed = speed or 1.0
|
||||||
|
|
||||||
-- Add transmission to queue.
|
-- Add transmission to queue.
|
||||||
self:AddTransmission(transmission)
|
self:AddTransmission(transmission)
|
||||||
@@ -2308,9 +2541,9 @@ function MSRSQUEUE:Broadcast(transmission)
|
|||||||
self:T(self.lid.."Broadcast")
|
self:T(self.lid.."Broadcast")
|
||||||
|
|
||||||
if transmission.frequency then
|
if transmission.frequency then
|
||||||
transmission.msrs:PlayTextExt(transmission.text, nil, transmission.frequency, transmission.modulation, transmission.gender, transmission.culture, transmission.voice, transmission.volume, transmission.label, transmission.coordinate)
|
transmission.msrs:PlayTextExt(transmission.text, nil, transmission.frequency, transmission.modulation, transmission.gender, transmission.culture, transmission.voice, transmission.volume, transmission.label, transmission.coordinate, transmission.speed)
|
||||||
else
|
else
|
||||||
transmission.msrs:PlayText(transmission.text,nil,transmission.coordinate)
|
transmission.msrs:PlayText(transmission.text,nil,transmission.coordinate,transmission.speed)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function texttogroup(gid)
|
local function texttogroup(gid)
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
do -- Sound Base
|
do -- Sound Base
|
||||||
|
|
||||||
-- @type SOUNDBASE
|
--- @type SOUNDBASE
|
||||||
-- @field #string ClassName Name of the class.
|
-- @field #string ClassName Name of the class.
|
||||||
-- @extends Core.Base#BASE
|
-- @extends Core.Base#BASE
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ end
|
|||||||
|
|
||||||
do -- Sound File
|
do -- Sound File
|
||||||
|
|
||||||
-- @type SOUNDFILE
|
--- @type SOUNDFILE
|
||||||
-- @field #string ClassName Name of the class
|
-- @field #string ClassName Name of the class
|
||||||
-- @field #string filename Name of the flag.
|
-- @field #string filename Name of the flag.
|
||||||
-- @field #string path Directory path, where the sound file is located. This includes the final slash "/".
|
-- @field #string path Directory path, where the sound file is located. This includes the final slash "/".
|
||||||
@@ -292,6 +292,7 @@ end
|
|||||||
|
|
||||||
do -- Text-To-Speech
|
do -- Text-To-Speech
|
||||||
|
|
||||||
|
---
|
||||||
-- @type SOUNDTEXT
|
-- @type SOUNDTEXT
|
||||||
-- @field #string ClassName Name of the class
|
-- @field #string ClassName Name of the class
|
||||||
-- @field #string text Text to speak.
|
-- @field #string text Text to speak.
|
||||||
@@ -299,6 +300,7 @@ do -- Text-To-Speech
|
|||||||
-- @field #string gender Gender: "male", "female".
|
-- @field #string gender Gender: "male", "female".
|
||||||
-- @field #string culture Culture, e.g. "en-GB".
|
-- @field #string culture Culture, e.g. "en-GB".
|
||||||
-- @field #string voice Specific voice to use. Overrules `gender` and `culture` settings.
|
-- @field #string voice Specific voice to use. Overrules `gender` and `culture` settings.
|
||||||
|
-- @field #number speed Specific speed to be used.
|
||||||
-- @extends Core.Base#BASE
|
-- @extends Core.Base#BASE
|
||||||
|
|
||||||
|
|
||||||
@@ -362,7 +364,7 @@ do -- Text-To-Speech
|
|||||||
self:SetDuration(Duration or MSRS.getSpeechTime(Text))
|
self:SetDuration(Duration or MSRS.getSpeechTime(Text))
|
||||||
--self:SetGender()
|
--self:SetGender()
|
||||||
--self:SetCulture()
|
--self:SetCulture()
|
||||||
|
self:SetSpeed()
|
||||||
-- Debug info:
|
-- Debug info:
|
||||||
self:T(string.format("New SOUNDTEXT: text=%s, duration=%.1f sec", self.text, self.duration))
|
self:T(string.format("New SOUNDTEXT: text=%s, duration=%.1f sec", self.text, self.duration))
|
||||||
|
|
||||||
@@ -425,4 +427,15 @@ do -- Text-To-Speech
|
|||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Set to use a specific speed.
|
||||||
|
-- @param #SOUNDTEXT self
|
||||||
|
-- @param #number Speed
|
||||||
|
-- @return #SOUNDTEXT self
|
||||||
|
function SOUNDTEXT:SetSpeed(Speed)
|
||||||
|
|
||||||
|
self.speed = Speed or 1.0
|
||||||
|
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
@@ -606,7 +606,7 @@ AIRBASE.Syria = {
|
|||||||
--- Airbases of the Mariana Islands map
|
--- Airbases of the Mariana Islands map
|
||||||
--
|
--
|
||||||
-- * `AIRBASE.MarianaIslands.Andersen_AFB` Andersen AFB
|
-- * `AIRBASE.MarianaIslands.Andersen_AFB` Andersen AFB
|
||||||
-- * `AIRBASE.MarianaIslands.Antonio_B._Won_Pat_Intl` Antonio B. Won Pat Intl
|
-- * `AIRBASE.MarianaIslands.Antonio_B_Won_Pat_Intl` Antonio B. Won Pat Intl
|
||||||
-- * `AIRBASE.MarianaIslands.North_West_Field` North West Field
|
-- * `AIRBASE.MarianaIslands.North_West_Field` North West Field
|
||||||
-- * `AIRBASE.MarianaIslands.Olf_Orote` Olf Orote
|
-- * `AIRBASE.MarianaIslands.Olf_Orote` Olf Orote
|
||||||
-- * `AIRBASE.MarianaIslands.Pagan_Airstrip` Pagan Airstrip
|
-- * `AIRBASE.MarianaIslands.Pagan_Airstrip` Pagan Airstrip
|
||||||
@@ -617,7 +617,7 @@ AIRBASE.Syria = {
|
|||||||
-- @field MarianaIslands
|
-- @field MarianaIslands
|
||||||
AIRBASE.MarianaIslands = {
|
AIRBASE.MarianaIslands = {
|
||||||
["Andersen_AFB"] = "Andersen AFB",
|
["Andersen_AFB"] = "Andersen AFB",
|
||||||
["Antonio_B._Won_Pat_Intl"] = "Antonio B. Won Pat Intl",
|
["Antonio_B_Won_Pat_Intl"] = "Antonio B. Won Pat Intl",
|
||||||
["North_West_Field"] = "North West Field",
|
["North_West_Field"] = "North West Field",
|
||||||
["Olf_Orote"] = "Olf Orote",
|
["Olf_Orote"] = "Olf Orote",
|
||||||
["Pagan_Airstrip"] = "Pagan Airstrip",
|
["Pagan_Airstrip"] = "Pagan Airstrip",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
[](https://github.com/FlightControl-Master/MOOSE/actions/workflows/build-includes.yml)
|
[](https://github.com/FlightControl-Master/MOOSE/actions/workflows/build-includes.yml)
|
||||||
|
|
||||||
<img src="https://github.com/FlightControl-Master/MOOSE/blob/master/docs/images/classes/overview.jpg" alt="MOOSE" style="width:600px;"/>
|
<img src="https://github.com/FlightControl-Master/MOOSE/blob/master/docs/images/classes/overview.jpg" alt="MOOSE" style="width:600px;"/>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user