mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2026-08-29 18:15:31 +00:00
Merge branch 'develop' into FF/Ops
This commit is contained in:
@@ -40,6 +40,7 @@ do
|
||||
-- @field #string ClassName Class name.
|
||||
-- @field #number ID ID of this cargo.
|
||||
-- @field #string Name Name for menu.
|
||||
-- @field #string DisplayName Display name for menu/messages.
|
||||
-- @field #table Templates Table of #POSITIONABLE objects.
|
||||
-- @field #string CargoType Enumerator of Type.
|
||||
-- @field #boolean HasBeenMoved Flag for moving.
|
||||
@@ -66,6 +67,7 @@ CTLD_CARGO = {
|
||||
ClassName = "CTLD_CARGO",
|
||||
ID = 0,
|
||||
Name = "none",
|
||||
DisplayName = "none",
|
||||
Templates = {},
|
||||
CargoType = "none",
|
||||
HasBeenMoved = false,
|
||||
@@ -125,6 +127,7 @@ CTLD_CARGO = {
|
||||
self:T({ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped})
|
||||
self.ID = ID or math.random(100000,1000000)
|
||||
self.Name = Name or "none" -- #string
|
||||
self.DisplayName = Name or "none" -- #string
|
||||
self.Templates = Templates or {} -- #table
|
||||
self.CargoType = Sorte or "type" -- #CTLD_CARGO.Enum
|
||||
self.HasBeenMoved = HasBeenMoved or false -- #boolean
|
||||
@@ -254,6 +257,26 @@ CTLD_CARGO = {
|
||||
return self.Name
|
||||
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.
|
||||
-- @param #CTLD_CARGO self
|
||||
-- @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.
|
||||
--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_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
|
||||
["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
|
||||
@@ -1624,6 +1647,9 @@ function CTLD:New(Coalition, Prefixes, Alias)
|
||||
self.Cargo_Crates = {}
|
||||
self.Cargo_Troops = {}
|
||||
self.Cargo_Statics = {}
|
||||
self._troopsByName = {}
|
||||
self._crateOrStaticByName = {}
|
||||
self._cargoByTemplate = {}
|
||||
self.Loaded_Cargo = {}
|
||||
self.Spawned_Crates = {}
|
||||
self.Spawned_Cargo = {}
|
||||
@@ -2137,6 +2163,35 @@ function CTLD:_GetUnitCapabilities(Unit)
|
||||
return capabilities
|
||||
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.
|
||||
-- @param #CTLD self
|
||||
-- @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
|
||||
-- @param #CTLD self
|
||||
function CTLD:IsC130J(Unit)
|
||||
function CTLD:IsC130J(Unit, IgnoreUseC130Flag)
|
||||
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 {}
|
||||
local unitname = Unit:GetName() or "none"
|
||||
return self.C130JUnits[unitname] == true
|
||||
@@ -2395,10 +2450,16 @@ end
|
||||
-- @return #CTLD_CARGO Cargo object, nil if it cannot be found
|
||||
function CTLD:_FindTroopsCargoObject(Name)
|
||||
self:T(self.lid .. " _FindTroopsCargoObject")
|
||||
self._troopsByName = self._troopsByName or {}
|
||||
local cached = self._troopsByName[Name]
|
||||
if cached then
|
||||
return cached
|
||||
end
|
||||
local cargo = nil
|
||||
for _,_cargo in pairs(self.Cargo_Troops)do
|
||||
local cargo = _cargo -- #CTLD_CARGO
|
||||
if cargo.Name == Name then
|
||||
self._troopsByName[Name] = cargo
|
||||
return cargo
|
||||
end
|
||||
end
|
||||
@@ -2411,16 +2472,23 @@ end
|
||||
-- @return #CTLD_CARGO Cargo object, nil if it cannot be found
|
||||
function CTLD:_FindCratesCargoObject(Name)
|
||||
self:T(self.lid .. " _FindCratesCargoObject")
|
||||
self._crateOrStaticByName = self._crateOrStaticByName or {}
|
||||
local cached = self._crateOrStaticByName[Name]
|
||||
if cached then
|
||||
return cached
|
||||
end
|
||||
local cargo = nil
|
||||
for _,_cargo in pairs(self.Cargo_Crates)do
|
||||
local cargo = _cargo -- #CTLD_CARGO
|
||||
if cargo.Name == Name then
|
||||
self._crateOrStaticByName[Name] = cargo
|
||||
return cargo
|
||||
end
|
||||
end
|
||||
for _,_cargo in pairs(self.Cargo_Statics)do
|
||||
local cargo = _cargo -- #CTLD_CARGO
|
||||
if cargo.Name == Name then
|
||||
self._crateOrStaticByName[Name] = cargo
|
||||
return cargo
|
||||
end
|
||||
end
|
||||
@@ -2546,6 +2614,20 @@ function CTLD:PreloadCrates(Unit,Cratesname,NumberOfCrates)
|
||||
return self
|
||||
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.
|
||||
-- @param #CTLD self
|
||||
-- @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)
|
||||
return
|
||||
else
|
||||
if not self:CanGetTroops(Group, Unit, Cargotype, 1, Inject) then
|
||||
return self
|
||||
end
|
||||
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)
|
||||
self:T({cargotype=loadcargotype})
|
||||
@@ -2935,6 +3020,8 @@ function CTLD:_LoadTroopsQuantity(Group, Unit, Cargo, quantity)
|
||||
if not self.debug then return self end
|
||||
end
|
||||
|
||||
if not self:CanGetTroops(Group, Unit, Cargo, n, false) then return self end
|
||||
|
||||
local prevSuppress = self.suppressmessages
|
||||
self.suppressmessages = true
|
||||
for i = 1, n do
|
||||
@@ -3100,15 +3187,29 @@ function CTLD:_AddCrateQuantityMenus(Group, Unit, parentMenu, cargoObj, stockSum
|
||||
|
||||
if maxQuantity == 1 then
|
||||
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 isHerc = self:IsC130J(Unit)
|
||||
local isHook = self:IsHook(Unit)
|
||||
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 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
|
||||
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)
|
||||
else
|
||||
local msg
|
||||
@@ -3119,8 +3220,9 @@ function CTLD:_AddCrateQuantityMenus(Group, Unit, parentMenu, cargoObj, stockSum
|
||||
msg = "Crate limit reached"
|
||||
end
|
||||
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
|
||||
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)
|
||||
end
|
||||
end
|
||||
@@ -3133,29 +3235,17 @@ function CTLD:_AddCrateQuantityMenus(Group, Unit, parentMenu, cargoObj, stockSum
|
||||
self:T("_AddCrateQuantityMenus maxQuantity "..maxQuantity.." Menu for MaxQ>1")
|
||||
local label = tostring(quantity)
|
||||
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 isHerc = self:IsC130J(Unit)
|
||||
local isHook = self:IsHook(Unit)
|
||||
local cgotype = cargoObj:GetType() or nil
|
||||
local suppressGetAndLoad = (self.enableChinookGCLoading == true) and isHook and (cgotype == CTLD_CARGO.Enum.STATIC)
|
||||
local canPartiallyLoad=((not capacityCrates or capacityCrates>=1)and(not maxMassCrates or maxMassCrates>=1))
|
||||
local suppressGetAndLoad = (self.enableChinookGCLoading == true) and (cgotype == CTLD_CARGO.Enum.STATIC)
|
||||
|
||||
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)
|
||||
else
|
||||
local msg
|
||||
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
|
||||
MENU_GROUP_COMMAND:New(Group, label, parentMenu, self._GetCrateQuantity, self, Group, Unit, cargoObj, quantity)
|
||||
end
|
||||
end
|
||||
return self
|
||||
@@ -3322,7 +3412,6 @@ end
|
||||
function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppressGetEvent)
|
||||
self:T(self.lid .. " _GetCrates")
|
||||
|
||||
-- check if we have stock
|
||||
local perSet = Cargo:GetCratesNeeded() or 1
|
||||
if perSet < 1 then perSet = 1 end
|
||||
local requestNumber = tonumber(number)
|
||||
@@ -3338,7 +3427,6 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppress
|
||||
local cgoname = Cargo:GetName()
|
||||
local instock = Cargo:GetStock()
|
||||
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)
|
||||
return false
|
||||
end
|
||||
@@ -3355,7 +3443,6 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppress
|
||||
if not drop and not pack then
|
||||
inzone = self:IsUnitInZone(Unit, CTLD.CargoZoneType.LOAD)
|
||||
if not inzone then
|
||||
---@diagnostic disable-next-line: cast-local-type
|
||||
inzone, ship, zone, distance, width = self:IsUnitInZone(Unit, CTLD.CargoZoneType.SHIP)
|
||||
end
|
||||
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 canloadcratesno = capabilities.cratelimit
|
||||
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
|
||||
self:_SendMessage("There are enough crates nearby already! Take care of those first!", 10, false, Group)
|
||||
return false
|
||||
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
|
||||
end
|
||||
-- spawn crates in front of helicopter
|
||||
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 IsHelo = Unit and Unit.IsHelicopter and Unit:IsHelicopter() or false
|
||||
local IsTruck = Unit:IsGround()
|
||||
local cargotype = Cargo -- Ops.CTLD#CTLD_CARGO
|
||||
local number = requestNumber --#number
|
||||
local cratesneeded = cargotype:GetCratesNeeded() --#number
|
||||
local cratename = cargotype:GetName()
|
||||
local cratedisplayname = self:_GetCargoDisplayName(cargotype)
|
||||
local cratetemplate = "Container"-- #string
|
||||
local cgotype = cargotype:GetType()
|
||||
local cgomass = cargotype:GetMass()
|
||||
@@ -3433,28 +3522,123 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppress
|
||||
heading = (heading + addon) % 360
|
||||
local row = 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)
|
||||
if self.placeCratesAhead == true then
|
||||
cratedistance = initialdist
|
||||
end
|
||||
|
||||
-- loop crates needed
|
||||
local cratecoord = nil -- Core.Point#COORDINATE
|
||||
for i=1,number do
|
||||
local cratealias = string.format("%s-%s-%d", cratename, cratetemplate, math.random(1,100000))
|
||||
if not self.placeCratesAhead or drop == true then
|
||||
cratedistance = (i-1)*2.5 + capabilities.length
|
||||
if cratedistance > self.CrateDistance then cratedistance = self.CrateDistance end
|
||||
-- altered heading logic
|
||||
-- DONE: right standard deviation?
|
||||
if self:IsUnitInAir(Unit) and self:IsFixedWing(Unit) then
|
||||
rheading = math.random(20,60)
|
||||
else
|
||||
rheading = UTILS.RandomGaussian(0, 30, -90, 90, 100)
|
||||
local cratecoord = nil
|
||||
local shipdist = nil
|
||||
local shipoffset = nil
|
||||
|
||||
local FW_STEP_BY_TYPE = {
|
||||
cds_crate = 2.0,
|
||||
cds_barrels = 2.0,
|
||||
ammo_cargo = 1.0,
|
||||
iso_container_small = 3.5,
|
||||
iso_container = 3.5,
|
||||
uh1h_cargo = 2.0,
|
||||
container_cargo = 2.5,
|
||||
}
|
||||
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
|
||||
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)
|
||||
cratecoord = position:Translate(cratedistance, rheading)
|
||||
end
|
||||
else
|
||||
cratedistance = (row - 1) * 6
|
||||
rheading = 90
|
||||
@@ -3466,18 +3650,19 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppress
|
||||
end
|
||||
end
|
||||
|
||||
--local cratevec2 = cratecoord:GetVec2()
|
||||
self.CrateCounter = self.CrateCounter + 1
|
||||
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) then
|
||||
if Cargo.C130TypeName then
|
||||
basetype = Cargo.C130TypeName
|
||||
elseif self.C130basetype and (not CType or CType == self.basetype) then
|
||||
basetype = self.C130basetype
|
||||
local crateSpawnHeading = 270
|
||||
if (IsHerc or IsHelo) and not drop and cratecoord and type(ship) ~= "string" then
|
||||
if math.abs(currentAngleOffset) < 0.01 then
|
||||
if not fwZeroAngleSetHeading then
|
||||
fwZeroAngleSetHeading = heading
|
||||
end
|
||||
crateSpawnHeading = fwZeroAngleSetHeading
|
||||
else
|
||||
crateSpawnHeading = fwNonZeroAngleSetHeading
|
||||
end
|
||||
end
|
||||
|
||||
if type(ship) == "string" then
|
||||
self:T("Spawning on ship "..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 dist = shipcoord:Get2DDistance(unitcoord)
|
||||
dist = dist - (20 + math.random(1, 10))
|
||||
local width = width / 2
|
||||
local Offy = math.random(-width,width)
|
||||
local halfwidth = (width or 20) / 2
|
||||
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)
|
||||
:InitCargoMass(cgomass)
|
||||
:InitCargo(self.enableslingload)
|
||||
@@ -3498,7 +3701,7 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppress
|
||||
local map = cargotype:GetStaticResourceMap()
|
||||
spawnstatic.TemplateStaticUnit.resourcePayload = map
|
||||
end
|
||||
self.Spawned_Crates[self.CrateCounter] = spawnstatic:Spawn(270,cratealias)
|
||||
self.Spawned_Crates[self.CrateCounter] = spawnstatic:Spawn(crateSpawnHeading, cratealias)
|
||||
else
|
||||
local spawnstatic = SPAWNSTATIC:NewFromType(basetype, CCat, self.cratecountry)
|
||||
:InitCoordinate(cratecoord)
|
||||
@@ -3511,8 +3714,9 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppress
|
||||
local map = cargotype:GetStaticResourceMap()
|
||||
spawnstatic.TemplateStaticUnit.resourcePayload = map
|
||||
end
|
||||
self.Spawned_Crates[self.CrateCounter] = spawnstatic:Spawn(270,cratealias)
|
||||
self.Spawned_Crates[self.CrateCounter] = spawnstatic:Spawn(crateSpawnHeading, cratealias)
|
||||
end
|
||||
|
||||
local templ = cargotype:GetTemplates()
|
||||
local sorte = cargotype:GetType()
|
||||
local subcat = cargotype.Subcategory
|
||||
@@ -3520,7 +3724,8 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppress
|
||||
local realcargo = nil
|
||||
if drop then
|
||||
--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()
|
||||
realcargo:SetStaticResourceMap(map)
|
||||
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)
|
||||
else
|
||||
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()
|
||||
realcargo:SetStaticResourceMap(map)
|
||||
if cargotype.TypeNames then
|
||||
realcargo.TypeNames = UTILS.DeepCopy(cargotype.TypeNames)
|
||||
end
|
||||
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
|
||||
|
||||
if not drop and not pack then
|
||||
table.insert(obtainedcargo, realcargo)
|
||||
end
|
||||
@@ -3548,13 +3755,19 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop, pack, quiet, suppress
|
||||
table.insert(self.Spawned_Cargo, realcargo)
|
||||
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
|
||||
Cargo:RemoveStock(requestedSets)
|
||||
self:_RefreshCrateQuantityMenus(Group, Unit, Cargo)
|
||||
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
|
||||
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)
|
||||
else
|
||||
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))
|
||||
if distance<=finddist and(weight<=maxloadable or _ignoreweight)and restricted==false and cando==true and not hercInnerBlocked then
|
||||
index = index + 1
|
||||
table.insert(found, staticid, cargo)
|
||||
found[#found+1] = cargo
|
||||
maxloadable = maxloadable - weight
|
||||
end
|
||||
|
||||
@@ -4026,22 +4239,21 @@ end
|
||||
function CTLD:_CleanupTrackedCrates(crateIdsToRemove)
|
||||
local existingcrates = self.Spawned_Cargo -- #table
|
||||
local newexcrates = {}
|
||||
local remove = {}
|
||||
for _,_ID in pairs(crateIdsToRemove or {}) do
|
||||
remove[_ID] = true
|
||||
end
|
||||
for _,_crate in pairs(existingcrates) do
|
||||
local excrate = _crate -- #CTLD_CARGO
|
||||
local ID = excrate:GetID()
|
||||
local keep = true
|
||||
for _,_ID in pairs(crateIdsToRemove) do
|
||||
if ID == _ID then
|
||||
keep = false
|
||||
end
|
||||
end
|
||||
local keep = not remove[ID]
|
||||
-- remove destroyed crates here too
|
||||
local static = _crate:GetPositionable() -- Wrapper.Static#STATIC -- crates
|
||||
if not static or not static:IsAlive() then
|
||||
keep = false
|
||||
end
|
||||
if keep then
|
||||
table.insert(newexcrates,_crate)
|
||||
newexcrates[#newexcrates+1] = _crate
|
||||
end
|
||||
end
|
||||
self.Spawned_Cargo = nil
|
||||
@@ -5592,7 +5804,7 @@ function CTLD:_RefreshF10Menus()
|
||||
end
|
||||
for _, cargoObj in pairs(self.Cargo_Troops) do
|
||||
if not cargoObj.DontShowInMenu then
|
||||
local menutext = cargoObj.Name
|
||||
local menutext = self:_GetCargoDisplayName(cargoObj)
|
||||
local parent = troopsmenu
|
||||
if useTroopSubcats and cargoObj.Subcategory and subcatmenus[cargoObj.Subcategory] then
|
||||
parent = subcatmenus[cargoObj.Subcategory]
|
||||
@@ -5605,7 +5817,7 @@ function CTLD:_RefreshF10Menus()
|
||||
else
|
||||
for _, cargoObj in pairs(self.Cargo_Troops) do
|
||||
if not cargoObj.DontShowInMenu then
|
||||
local menutext = cargoObj.Name
|
||||
local menutext = self:_GetCargoDisplayName(cargoObj)
|
||||
local mSet = MENU_GROUP:New(_group, menutext, troopsmenu)
|
||||
_group.CTLD_TroopMenus[cargoObj.Name] = mSet
|
||||
self:_AddTroopQuantityMenus(_group,_unit,mSet,cargoObj)
|
||||
@@ -5620,7 +5832,7 @@ function CTLD:_RefreshF10Menus()
|
||||
if loadedData and loadedData.Cargo then
|
||||
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
|
||||
local name=cargoObj:GetName() or "Unknown"
|
||||
local name=self:_GetCargoDisplayName(cargoObj)
|
||||
local needed=cargoObj:GetCratesNeeded() or 1
|
||||
local cID=cargoObj:GetID()
|
||||
local line=string.format("Drop: %s",name,needed,cID)
|
||||
@@ -5673,10 +5885,11 @@ function CTLD:_RefreshF10Menus()
|
||||
local needed = cargoObj:GetCratesNeeded() or 1
|
||||
|
||||
local txt
|
||||
local cargoLabel = self:_GetCargoDisplayName(cargoObj)
|
||||
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
|
||||
txt = string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0)
|
||||
txt = string.format("%s (%dkg)",cargoLabel,cargoObj.PerCrateMass or 0)
|
||||
end
|
||||
if cargoObj.Location then txt = txt.."[R]" end
|
||||
if self.showstockinmenuitems then
|
||||
@@ -5721,10 +5934,11 @@ function CTLD:_RefreshF10Menus()
|
||||
if not cargoObj.DontShowInMenu then
|
||||
local needed = cargoObj:GetCratesNeeded() or 1
|
||||
local txt
|
||||
local cargoLabel = self:_GetCargoDisplayName(cargoObj)
|
||||
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
|
||||
txt = string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0)
|
||||
txt = string.format("%s (%dkg)",cargoLabel,cargoObj.PerCrateMass or 0)
|
||||
end
|
||||
if cargoObj.Location then txt = txt.."[R]" end
|
||||
local stock = cargoObj:GetStock()
|
||||
@@ -5736,10 +5950,11 @@ function CTLD:_RefreshF10Menus()
|
||||
if (not cargoObj.DontShowInMenu) and (not cargoObj.UnitCanCarry or cargoObj:UnitCanCarry(_unit)) then
|
||||
local needed = cargoObj:GetCratesNeeded() or 1
|
||||
local txt
|
||||
local cargoLabel = self:_GetCargoDisplayName(cargoObj)
|
||||
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
|
||||
txt = string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0)
|
||||
txt = string.format("%s (%dkg)",cargoLabel,cargoObj.PerCrateMass or 0)
|
||||
end
|
||||
if cargoObj.Location then txt = txt.."[R]" end
|
||||
local stock = cargoObj:GetStock()
|
||||
@@ -5752,10 +5967,11 @@ function CTLD:_RefreshF10Menus()
|
||||
if not cargoObj.DontShowInMenu then
|
||||
local needed = cargoObj:GetCratesNeeded() or 1
|
||||
local txt
|
||||
local cargoLabel = self:_GetCargoDisplayName(cargoObj)
|
||||
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
|
||||
txt = string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0)
|
||||
txt = string.format("%s (%dkg)",cargoLabel,cargoObj.PerCrateMass or 0)
|
||||
end
|
||||
if cargoObj.Location then txt = txt.."[R]" end
|
||||
local stock = cargoObj:GetStock()
|
||||
@@ -5767,10 +5983,11 @@ function CTLD:_RefreshF10Menus()
|
||||
if (not cargoObj.DontShowInMenu) and (not cargoObj.UnitCanCarry or cargoObj:UnitCanCarry(_unit)) then
|
||||
local needed = cargoObj:GetCratesNeeded() or 1
|
||||
local txt
|
||||
local cargoLabel = self:_GetCargoDisplayName(cargoObj)
|
||||
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
|
||||
txt = string.format("%s (%dkg)",cargoObj.Name,cargoObj.PerCrateMass or 0)
|
||||
txt = string.format("%s (%dkg)",cargoLabel,cargoObj.PerCrateMass or 0)
|
||||
end
|
||||
if cargoObj.Location then txt = txt.."[R]" end
|
||||
local stock = cargoObj:GetStock()
|
||||
@@ -5816,12 +6033,13 @@ function CTLD:_RefreshF10Menus()
|
||||
if cgo and (not cgo:WasDropped()) then
|
||||
local cname = cgo:GetName()
|
||||
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
|
||||
end
|
||||
end
|
||||
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)
|
||||
end
|
||||
end
|
||||
@@ -5857,7 +6075,7 @@ function CTLD:_RefreshF10Menus()
|
||||
end
|
||||
parent = sub
|
||||
end
|
||||
local menutext = cargoObj.Name
|
||||
local menutext = self:_GetCargoDisplayName(cargoObj)
|
||||
if type(cargoObj.Stock) == "number" and cargoObj.Stock >= 0 and self.showstockinmenuitems then
|
||||
menutext = menutext.."["..cargoObj.Stock.."]"
|
||||
end
|
||||
@@ -6758,6 +6976,16 @@ function CTLD:AddTroopsCargo(Name,Templates,Type,NoTroops,PerTroopMass,Stock,Sub
|
||||
-- Troops are directly loadable
|
||||
local cargo = CTLD_CARGO:New(self.CargoCounter,Name,Templates,Type,false,true,NoTroops,nil,nil,PerTroopMass,Stock, SubCategory)
|
||||
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
|
||||
return self
|
||||
end
|
||||
@@ -6855,6 +7083,16 @@ function CTLD:AddCratesCargo(Name,Templates,Type,NoCrates,PerCrateMass,Stock,Sub
|
||||
end
|
||||
cargo.C130TypeName = C130TypeName
|
||||
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
|
||||
return self
|
||||
end
|
||||
@@ -6895,6 +7133,16 @@ function CTLD:AddCratesCargoNoMove(Name,Templates,Type,NoCrates,PerCrateMass,Sto
|
||||
end
|
||||
cargo.C130TypeName = C130TypeName
|
||||
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 {}
|
||||
if type(Templates)=="table" then
|
||||
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 #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 DisplayName Display name shown in menus/messages (optional). Falls back to `Name`.
|
||||
-- @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.CargoCounter = self.CargoCounter + 1
|
||||
local type = CTLD_CARGO.Enum.STATIC
|
||||
local cargotype = CTLD_CARGO.Enum.STATIC
|
||||
local template = STATIC:FindByName(Name,true):GetTypeName()
|
||||
local unittemplate = _DATABASE:GetStaticUnitTemplate(Name)
|
||||
local ResourceMap = nil
|
||||
@@ -6927,12 +7177,69 @@ function CTLD:AddStaticsCargo(Name,Mass,Stock,SubCategory,DontShowInMenu,Locatio
|
||||
ResourceMap = UTILS.DeepCopy(unittemplate.resourcePayload)
|
||||
end
|
||||
-- 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
|
||||
cargo:AddUnitTypeName(UnitTypes)
|
||||
end
|
||||
cargo:SetDisplayName(DisplayName or Name)
|
||||
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
|
||||
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
|
||||
return cargo
|
||||
end
|
||||
@@ -6941,11 +7248,12 @@ end
|
||||
-- @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 #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
|
||||
function CTLD:GetStaticsCargoFromTemplate(Name,Mass)
|
||||
function CTLD:GetStaticsCargoFromTemplate(Name,Mass,DisplayName)
|
||||
self:T(self.lid .. " GetStaticsCargoFromTemplate")
|
||||
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 unittemplate = _DATABASE:GetStaticUnitTemplate(Name)
|
||||
local ResourceMap = nil
|
||||
@@ -6953,12 +7261,38 @@ function CTLD:GetStaticsCargoFromTemplate(Name,Mass)
|
||||
ResourceMap = UTILS.DeepCopy(unittemplate.resourcePayload)
|
||||
end
|
||||
-- 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)
|
||||
--table.insert(self.Cargo_Statics,cargo)
|
||||
return cargo
|
||||
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.
|
||||
-- @param #CTLD self
|
||||
-- @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)
|
||||
end
|
||||
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
|
||||
end
|
||||
|
||||
@@ -8429,11 +8773,17 @@ end
|
||||
template = string.gsub(GroupName,"#(%d+)$","")
|
||||
end
|
||||
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
|
||||
local comparison = ""
|
||||
if type(v.Templates) == "string" then comparison = v.Templates else comparison = v.Templates[1] end
|
||||
if comparison == template then
|
||||
Cargotype = v
|
||||
self._cargoByTemplate[template] = v
|
||||
break
|
||||
end
|
||||
end
|
||||
@@ -8443,6 +8793,7 @@ 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
|
||||
Cargotype = v
|
||||
self._cargoByTemplate[template] = v
|
||||
break
|
||||
end
|
||||
end
|
||||
@@ -8867,14 +9218,13 @@ end
|
||||
-- @return #CTLD self
|
||||
function CTLD:onafterStatus(From, Event, To)
|
||||
self:T({From, Event, To})
|
||||
-- gather some stats
|
||||
-- pilots
|
||||
if self.debug or self.verbose > 0 then
|
||||
-- gather stats only when logging is enabled
|
||||
local pilots = 0
|
||||
for _,_pilot in pairs (self.CtldUnits) do
|
||||
pilots = pilots + 1
|
||||
end
|
||||
|
||||
-- spawned cargo boxes curr in field
|
||||
local boxes = 0
|
||||
for _,_pilot in pairs (self.Spawned_Cargo) do
|
||||
boxes = boxes + 1
|
||||
@@ -8882,8 +9232,6 @@ end
|
||||
|
||||
local cc = self.CargoCounter
|
||||
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 m = MESSAGE:New(text,10,"CTLD"):ToAll()
|
||||
if self.verbose > 0 then
|
||||
|
||||
@@ -50,8 +50,8 @@
|
||||
-- @field #string ConfigFilePath Path to the standard config file.
|
||||
-- @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 #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 provider Provider of TTS (win, gcloud, azure, amazon, ...).
|
||||
-- @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
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
@@ -67,7 +67,8 @@
|
||||
--
|
||||
-- * This script needs SRS version >= 1.9.6
|
||||
-- * 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
|
||||
--
|
||||
@@ -77,7 +78,7 @@
|
||||
-- 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.
|
||||
-- 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
|
||||
--
|
||||
@@ -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
|
||||
-- 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
|
||||
--
|
||||
-- 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.
|
||||
--
|
||||
-- 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.
|
||||
-- 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
|
||||
-- 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.
|
||||
-- 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 support
|
||||
-- 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
|
||||
-- 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):
|
||||
--
|
||||
-- -- 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
|
||||
-- MSRS.SetDefaultBackendGRPC()
|
||||
-- -- Create a SOUNDTEXT object.
|
||||
-- local text=SOUNDTEXT:New("All Enemies destroyed")
|
||||
-- -- MOOSE SRS
|
||||
-- local msrs=MSRS:New('', 305.0)
|
||||
-- local msrs=MSRS:New('ExampleInstance', 305.0)
|
||||
-- -- Text-to speech with default voice after 30 seconds.
|
||||
-- msrs:PlaySoundText(text, 30)
|
||||
--
|
||||
-- Basic example of using another class (ATIS) with SRS and the DCS-gRPC backend (DCS-gRPC not previously started):
|
||||
--
|
||||
-- -- 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
|
||||
-- MSRS.SetDefaultBackendGRPC()
|
||||
-- -- Create new ATIS as usual
|
||||
@@ -262,7 +280,7 @@ MSRS = {
|
||||
|
||||
--- MSRS class version.
|
||||
-- @field #string version
|
||||
MSRS.version="0.3.3"
|
||||
MSRS.version="0.3.4"
|
||||
|
||||
--- Voices
|
||||
-- @type MSRS.Voices
|
||||
@@ -699,9 +717,11 @@ MSRS.Voices = {
|
||||
-- @type MSRS.Backend
|
||||
-- @field #string SRSEXE Use `DCS-SR-ExternalAudio.exe`.
|
||||
-- @field #string GRPC Use DCS-gRPC.
|
||||
-- @field #string GRPC Use Hound-TTS.
|
||||
MSRS.Backend = {
|
||||
SRSEXE = "srsexe",
|
||||
GRPC = "grpc",
|
||||
HOUND = "hound",
|
||||
}
|
||||
|
||||
--- 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 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 PIPER Piper local voice service. Only possible with Hound-TTS backend.
|
||||
MSRS.Provider = {
|
||||
WINDOWS = "win",
|
||||
GOOGLE = "gcloud",
|
||||
AZURE = "azure",
|
||||
AMAZON = "aws",
|
||||
PIPER = "piper",
|
||||
}
|
||||
|
||||
--- Function for UUID.
|
||||
@@ -760,6 +782,7 @@ end
|
||||
-- DONE: Add google.
|
||||
-- DONE: Add gRPC google options
|
||||
-- DONE: Add loading default config file
|
||||
-- DONE: Add Hound TTS
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
@@ -874,6 +897,16 @@ function MSRS:SetBackendGRPC()
|
||||
return self
|
||||
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.
|
||||
-- @param #MSRS self
|
||||
-- @return #MSRS self
|
||||
@@ -896,6 +929,12 @@ function MSRS.SetDefaultBackendGRPC()
|
||||
MSRS.backend=MSRS.Backend.GRPC
|
||||
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.
|
||||
-- @param #MSRS self
|
||||
-- @return #string Backend.
|
||||
@@ -1147,6 +1186,16 @@ function MSRS:SetVoiceGoogle(Voice)
|
||||
return self
|
||||
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.
|
||||
-- @param #MSRS self
|
||||
@@ -1241,9 +1290,10 @@ end
|
||||
-- - `MSRS.Provider.WINDOWS`: Microsoft Windows (default)
|
||||
-- - `MSRS.Provider.GOOGLE`: Google Cloud
|
||||
-- - `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 #string Provider
|
||||
@@ -1390,6 +1440,15 @@ function MSRS:SetTTSProviderAmazon()
|
||||
return self
|
||||
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.
|
||||
-- @param #MSRS self
|
||||
@@ -1473,8 +1532,11 @@ function MSRS:PlaySoundText(SoundText, Delay)
|
||||
self:ScheduleOnce(Delay, MSRS.PlaySoundText, self, SoundText, 0)
|
||||
else
|
||||
|
||||
-- TODO Insert HOUND option
|
||||
if self.backend==MSRS.Backend.GRPC then
|
||||
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
|
||||
|
||||
-- Get command.
|
||||
@@ -1498,8 +1560,9 @@ end
|
||||
-- @param #string Text Text message.
|
||||
-- @param #number Delay Delay in seconds, before the message is played.
|
||||
-- @param Core.Point#COORDINATE Coordinate Coordinate.
|
||||
-- @param #number Speed
|
||||
-- @return #MSRS self
|
||||
function MSRS:PlayText(Text, Delay, Coordinate)
|
||||
function MSRS:PlayText(Text, Delay, Coordinate, Speed)
|
||||
self:F( {Text, Delay, Coordinate} )
|
||||
|
||||
if Delay and Delay>0 then
|
||||
@@ -1509,8 +1572,10 @@ function MSRS:PlayText(Text, Delay, Coordinate)
|
||||
if self.backend==MSRS.Backend.GRPC then
|
||||
self:T(self.lid.."Transmitting")
|
||||
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
|
||||
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
|
||||
@@ -1530,12 +1595,13 @@ end
|
||||
-- @param #number Volume Volume.
|
||||
-- @param #string Label Label.
|
||||
-- @param Core.Point#COORDINATE Coordinate Coordinate.
|
||||
-- @param #number Speed Speed.
|
||||
-- @return #MSRS self
|
||||
function MSRS:PlayTextExt(Text, Delay, Frequencies, Modulations, Gender, Culture, Voice, Volume, Label, Coordinate)
|
||||
self:T({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, Speed} )
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
@@ -1871,7 +1944,7 @@ end
|
||||
function MSRS:_DCSgRPCtts(Text, Frequencies, Gender, Culture, Voice, Volume, Label, Coordinate)
|
||||
|
||||
-- Debug info.
|
||||
self:T("MSRS_BACKEND_DCSGRPC:_DCSgRPCtts()")
|
||||
self:T("MSRS_BACKEND_DCSGRPC:_DCSgRPCtts")
|
||||
self:T({Text, Frequencies, Gender, Culture, Voice, Volume, Label, Coordinate})
|
||||
|
||||
local options = {} -- #MSRS.GRPCOptions
|
||||
@@ -1881,7 +1954,7 @@ function MSRS:_DCSgRPCtts(Text, Frequencies, Gender, Culture, Voice, Volume, Lab
|
||||
-- Get frequenceies.
|
||||
Frequencies = UTILS.EnsureTable(Frequencies, true) or self:GetFrequencies()
|
||||
|
||||
-- Plain text (not really used.
|
||||
-- Plain text - not really used.
|
||||
options.plaintext=Text
|
||||
|
||||
-- Name shows as sender.
|
||||
@@ -1938,6 +2011,156 @@ function MSRS:_DCSgRPCtts(Text, Frequencies, Gender, Culture, Voice, Volume, Lab
|
||||
|
||||
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
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -1957,7 +2180,7 @@ end
|
||||
-- MSRS_Config = {
|
||||
-- Path = "C:\\Program Files\\DCS-SimpleRadio-Standalone\\ExternalAudio", -- Path to SRS install directory.
|
||||
-- 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!
|
||||
-- Modulation = {0,0}, -- Default modulations. Must be a table, 1..n entries, one for each frequency!
|
||||
-- Volume = 1.0, -- Default volume [0,1].
|
||||
@@ -1967,7 +2190,7 @@ end
|
||||
-- Gender = "male",
|
||||
-- Voice = "Microsoft Hazel Desktop", -- Voice that is used if no explicit provider voice is specified.
|
||||
-- Label = "MSRS",
|
||||
-- Provider = "win", --Provider for generating TTS (win, gcloud, azure, aws).
|
||||
-- Provider = "win", --Provider for generating TTS (win, gcloud, azure, aws, piper).
|
||||
-- -- Windows
|
||||
-- win = {
|
||||
-- voice = "Microsoft Hazel Desktop",
|
||||
@@ -2092,6 +2315,11 @@ end
|
||||
-- @param #boolean isGoogle We're using Google TTS
|
||||
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
|
||||
|
||||
speed = speed or 1.0
|
||||
@@ -2117,6 +2345,8 @@ function MSRS.getSpeechTime(length,speed,isGoogle)
|
||||
end
|
||||
|
||||
return length/cps --math.ceil(length/cps)
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@@ -2168,6 +2398,7 @@ MSRSQUEUE = {
|
||||
-- @field #number volume Volume
|
||||
-- @field #string label Label to be used
|
||||
-- @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.
|
||||
-- @param #MSRSQUEUE self
|
||||
@@ -2252,9 +2483,10 @@ end
|
||||
-- @param #number volume Volume setting
|
||||
-- @param #string label Label 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.
|
||||
function MSRSQUEUE:NewTransmission(text, duration, msrs, tstart, interval, subgroups, subtitle, subduration, frequency, modulation, gender, culture, voice, volume, label,coordinate)
|
||||
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})
|
||||
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, S=speed})
|
||||
if self.TransmitOnlyWithPlayers then
|
||||
if self.PlayerSet and self.PlayerSet:CountAlive() == 0 then
|
||||
return self
|
||||
@@ -2294,6 +2526,7 @@ function MSRSQUEUE:NewTransmission(text, duration, msrs, tstart, interval, subgr
|
||||
transmission.volume = volume or msrs.volume
|
||||
transmission.label = label or msrs.Label
|
||||
transmission.coordinate = coordinate or msrs.coordinate
|
||||
transmission.speed = speed or 1.0
|
||||
|
||||
-- Add transmission to queue.
|
||||
self:AddTransmission(transmission)
|
||||
@@ -2308,9 +2541,9 @@ function MSRSQUEUE:Broadcast(transmission)
|
||||
self:T(self.lid.."Broadcast")
|
||||
|
||||
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
|
||||
transmission.msrs:PlayText(transmission.text,nil,transmission.coordinate)
|
||||
transmission.msrs:PlayText(transmission.text,nil,transmission.coordinate,transmission.speed)
|
||||
end
|
||||
|
||||
local function texttogroup(gid)
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
do -- Sound Base
|
||||
|
||||
-- @type SOUNDBASE
|
||||
--- @type SOUNDBASE
|
||||
-- @field #string ClassName Name of the class.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
@@ -100,7 +100,7 @@ end
|
||||
|
||||
do -- Sound File
|
||||
|
||||
-- @type SOUNDFILE
|
||||
--- @type SOUNDFILE
|
||||
-- @field #string ClassName Name of the class
|
||||
-- @field #string filename Name of the flag.
|
||||
-- @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
|
||||
|
||||
---
|
||||
-- @type SOUNDTEXT
|
||||
-- @field #string ClassName Name of the class
|
||||
-- @field #string text Text to speak.
|
||||
@@ -299,6 +300,7 @@ do -- Text-To-Speech
|
||||
-- @field #string gender Gender: "male", "female".
|
||||
-- @field #string culture Culture, e.g. "en-GB".
|
||||
-- @field #string voice Specific voice to use. Overrules `gender` and `culture` settings.
|
||||
-- @field #number speed Specific speed to be used.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
|
||||
@@ -362,7 +364,7 @@ do -- Text-To-Speech
|
||||
self:SetDuration(Duration or MSRS.getSpeechTime(Text))
|
||||
--self:SetGender()
|
||||
--self:SetCulture()
|
||||
|
||||
self:SetSpeed()
|
||||
-- Debug info:
|
||||
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
|
||||
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
|
||||
@@ -606,7 +606,7 @@ AIRBASE.Syria = {
|
||||
--- Airbases of the Mariana Islands map
|
||||
--
|
||||
-- * `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.Olf_Orote` Olf Orote
|
||||
-- * `AIRBASE.MarianaIslands.Pagan_Airstrip` Pagan Airstrip
|
||||
@@ -617,7 +617,7 @@ AIRBASE.Syria = {
|
||||
-- @field MarianaIslands
|
||||
AIRBASE.MarianaIslands = {
|
||||
["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",
|
||||
["Olf_Orote"] = "Olf Orote",
|
||||
["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;"/>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user