mirror of
https://github.com/weyne85/DML.git
synced 2025-10-29 16:57:49 +00:00
Version 1.3.0
FlareZone PlayerZone shallows
This commit is contained in:
parent
97d3c10540
commit
83f9478f86
Binary file not shown.
Binary file not shown.
113
modules/asw.lua
113
modules/asw.lua
@ -1,5 +1,5 @@
|
||||
asw = {}
|
||||
asw.version = "1.0.0"
|
||||
asw.version = "1.0.1"
|
||||
asw.verbose = false
|
||||
asw.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
@ -15,6 +15,7 @@ asw.fixes = {} -- all subs that we have a fix on. indexed by sub name
|
||||
--[[--
|
||||
Version History
|
||||
1.0.0 - initial version
|
||||
1.0.1 - integration with playerScore
|
||||
|
||||
--]]--
|
||||
|
||||
@ -36,6 +37,10 @@ function asw.createTorpedoForUnit(theUnit)
|
||||
local t = asw.createTorpedo()
|
||||
t.coalition = theUnit:getCoalition()
|
||||
t.point = theUnit:getPoint()
|
||||
t.droppedBy = theUnit
|
||||
if theUnit.getPlayerName and theUnit:getPlayerName() ~= nil then
|
||||
t.playerName = theUnit:getPlayerName()
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
@ -716,6 +721,7 @@ function asw.updateTorpedo(theTorpedo, allSubs)
|
||||
end
|
||||
|
||||
if dist < 1.2 * displacement then
|
||||
theTorpedo.target = theSub
|
||||
theTorpedo.state = 99 -- go boom
|
||||
end
|
||||
markTorpedo(theTorpedo)
|
||||
@ -762,10 +768,26 @@ function asw.updateTorpedo(theTorpedo, allSubs)
|
||||
return true
|
||||
elseif theTorpedo.state == 99 then -- go boom
|
||||
if Unit.isExist(theTorpedo.target) then
|
||||
if asw.verbose then
|
||||
trigger.action.outText("99 torpedoes have target", 30)
|
||||
end
|
||||
|
||||
-- interface to playerScore
|
||||
if cfxPlayerScore then
|
||||
asw.doScore(theTorpedo)
|
||||
else
|
||||
if asw.verbose then
|
||||
trigger.action.outText("No playerScore present", 30)
|
||||
end
|
||||
end
|
||||
Unit.destroy(theTorpedo.target)
|
||||
else
|
||||
if asw.verbose then
|
||||
trigger.action.outText("t99 no target exist", 30)
|
||||
end
|
||||
end
|
||||
-- impact!
|
||||
trigger.action.outTextForCoalition(theTorpedo.coalition, "Impact for " .. theTorpedo.name .. "! We have confirmed hit on submerged contact!", 30)
|
||||
trigger.action.outTextForCoalition(theTorpedo.coalition, "Impact for " .. theTorpedo.name .. "! We have confirmed hit on submerged contact!", 30)
|
||||
if theTorpedo.coalition == 1 then
|
||||
if asw.redKill then
|
||||
cfxZones.pollFlag(asw.redKill, asw.method, asw)
|
||||
@ -796,6 +818,86 @@ function asw.updateTorpedo(theTorpedo, allSubs)
|
||||
return true
|
||||
end
|
||||
|
||||
-- PlayerScore interface
|
||||
function processFeat(inMsg, playerUnit, victim, timeFormat)
|
||||
if not inMsg then return "<nil inMsg>" end
|
||||
-- replace <t> with current mission time HMS
|
||||
local absSecs = timer.getAbsTime()-- + env.mission.start_time
|
||||
while absSecs > 86400 do
|
||||
absSecs = absSecs - 86400 -- subtract out all days
|
||||
end
|
||||
if not timeFormat then timeFormat = "<:h>:<:m>:<:s>" end
|
||||
-- <t>
|
||||
local timeString = dcsCommon.processHMS(timeFormat, absSecs)
|
||||
local outMsg = inMsg:gsub("<t>", timeString)
|
||||
-- <n>
|
||||
outMsg = dcsCommon.processStringWildcards(outMsg) -- <n>
|
||||
-- <unit, type, player etc>
|
||||
outMsg = cfxPlayerScore.preprocessWildcards(outMsg, playerUnit, victim)
|
||||
return outMsg
|
||||
end
|
||||
|
||||
function asw.doScore(theTorpedo)
|
||||
if asw.verbose then
|
||||
trigger.action.outText("asw: enter doScore", 30)
|
||||
end
|
||||
-- make sure that this is a player-dropped torpedo
|
||||
if not theTorpedo then
|
||||
if asw.verbose then
|
||||
trigger.action.outText("no torpedo", 30)
|
||||
end
|
||||
return
|
||||
end
|
||||
local theUnit = theTorpedo.target
|
||||
if not theTorpedo.playerName then
|
||||
if asw.verbose then
|
||||
trigger.action.outText("no torpedo", 30)
|
||||
end
|
||||
return
|
||||
end
|
||||
local pName = theTorpedo.playerName
|
||||
-- make sure that the player's original unit still exists
|
||||
if not (theTorpedo.droppedBy and Unit.isExist(theTorpedo.droppedBy)) then
|
||||
if asw.verbose then
|
||||
trigger.action.outText("torpedo dropper dead", 30)
|
||||
end
|
||||
return -- torpedo-dropping unit did not survive
|
||||
end
|
||||
|
||||
local fratricide = (theTorpedo.coalition == theUnit:getCoalition())
|
||||
if fratricide then
|
||||
if asw.verbose then
|
||||
trigger.action.outText("+++asw: fratricide detected", 30)
|
||||
end
|
||||
end
|
||||
|
||||
if asw.killScore > 0 then
|
||||
-- award score
|
||||
local score = asw.killScore
|
||||
if fratricide then score = -1 * score end
|
||||
cfxPlayerScore.logKillForPlayer(pName, theUnit)
|
||||
cfxPlayerScore.awardScoreTo(theTorpedo.coalition, score, pName)
|
||||
if asw.verbose then
|
||||
trigger.action.outText("updated score (" .. score .. ") for player <" .. pName .. ">", 30)
|
||||
end
|
||||
else
|
||||
if asw.verbose then
|
||||
trigger.action.outText("no score num defined", 30)
|
||||
end
|
||||
end
|
||||
|
||||
if asw.killFeat and (not fratricide) then
|
||||
-- we treat killFeat as boolean
|
||||
local theFeat = "Killed type <type> submerged vessel <unit> at <t>"
|
||||
theFeat = processFeat(theFeat, theTorpedo.droppedBy, theUnit)
|
||||
cfxPlayerScore.logFeatForPlayer(pName, theFeat)
|
||||
else
|
||||
if asw.verbose then
|
||||
trigger.action.outText("no feat defined or fratricide", 30)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- MAIN UPDATE
|
||||
--
|
||||
@ -940,10 +1042,17 @@ function asw.readConfigZone()
|
||||
|
||||
asw.smokeColor = cfxZones.getSmokeColorStringFromZoneProperty(theZone, "smokeColor", "red")
|
||||
asw.smokeColor = dcsCommon.smokeColor2Num(asw.smokeColor)
|
||||
|
||||
asw.killScore = cfxZones.getNumberFromZoneProperty(theZone, "killScore", 0)
|
||||
|
||||
if cfxZones.hasProperty(theZone, "killFeat") then
|
||||
asw.killFeat = cfxZones.getStringFromZoneProperty(theZone, "killFeat", "Sub Kill")
|
||||
end
|
||||
|
||||
if asw.verbose then
|
||||
trigger.action.outText("+++asw: read config", 30)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function asw.start()
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
aswGUI = {}
|
||||
aswGUI.version = "1.0.1"
|
||||
aswGUI.version = "1.0.2"
|
||||
aswGUI.verbose = false
|
||||
aswGUI.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
@ -12,6 +12,7 @@ aswGUI.requiredLibs = {
|
||||
Version History
|
||||
1.0.0 - initial version
|
||||
1.0.1 - env.info clean-up, verbosity clean-up
|
||||
1.0.2 - late start capability
|
||||
|
||||
--]]--
|
||||
|
||||
@ -524,6 +525,22 @@ function aswGUI:onEvent(theEvent)
|
||||
end
|
||||
end
|
||||
|
||||
function aswGUI.processPlayerUnit(theUnit)
|
||||
local name = theUnit:getName()
|
||||
local conf = aswGUI.aswCraft[name]
|
||||
if not conf then
|
||||
-- let's init it
|
||||
conf = aswGUI.initUnit(name)
|
||||
aswGUI.aswCraft[name] = conf
|
||||
else
|
||||
aswGUI.resetConf(conf)
|
||||
end
|
||||
aswGUI.setMenuForUnit(theUnit)
|
||||
if aswGUI.verbose then
|
||||
trigger.action.outText("aswG: set up player <" .. theUnit:getPlayerName() .. "> in <" .. name .. ">", 30)
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- Config & start
|
||||
--
|
||||
@ -571,7 +588,10 @@ function aswGUI.start()
|
||||
|
||||
-- subscribe to world events
|
||||
world.addEventHandler(aswGUI)
|
||||
|
||||
|
||||
-- install menus in all existing players
|
||||
dcsCommon.iteratePlayers(aswGUI.processPlayerUnit)
|
||||
|
||||
-- say Hi
|
||||
trigger.action.outText("cfx ASW GUI v" .. aswGUI.version .. " started.", 30)
|
||||
return true
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
cfxPlayerScore = {}
|
||||
cfxPlayerScore.version = "2.0.0"
|
||||
cfxPlayerScore.version = "2.0.1"
|
||||
cfxPlayerScore.name = "cfxPlayerScore" -- compatibility with flag bangers
|
||||
cfxPlayerScore.badSound = "Death BRASS.wav"
|
||||
cfxPlayerScore.scoreSound = "Quest Snare 3.wav"
|
||||
@ -70,7 +70,9 @@ cfxPlayerScore.firstSave = true -- to force overwrite
|
||||
- pkMod attribute
|
||||
- pvp feat
|
||||
- immediate awarding of all negative scores, even if deferred
|
||||
|
||||
2.0.1 - corrected access to nowString()
|
||||
- more robust config reading
|
||||
|
||||
--]]--
|
||||
|
||||
cfxPlayerScore.requiredLibs = {
|
||||
@ -1278,7 +1280,7 @@ function cfxPlayerScore.saveScores(theText, name)
|
||||
end
|
||||
|
||||
-- prepend time for score
|
||||
theText = "\n\n====== Mission Time: " .. dcsCommon.nowstring() .. "\n" .. theText
|
||||
theText = "\n\n====== Mission Time: " .. dcsCommon.nowString() .. "\n" .. theText
|
||||
end
|
||||
|
||||
if persistence.saveText(theText, name, shared, append) then
|
||||
@ -1360,10 +1362,11 @@ function cfxPlayerScore.start()
|
||||
local theZone = cfxZones.getZoneByName("playerScoreConfig")
|
||||
if not theZone then
|
||||
trigger.action.outText("+++scr: no config!", 30)
|
||||
else
|
||||
cfxPlayerScore.readConfigZone(theZone)
|
||||
trigger.action.outText("+++scr: read config", 30)
|
||||
theZone = cfxZones.createSimpleZone("playerScoreConfig")
|
||||
end
|
||||
cfxPlayerScore.readConfigZone(theZone)
|
||||
-- trigger.action.outText("+++scr: read config", 30)
|
||||
|
||||
|
||||
-- read all scoreSafe zones
|
||||
local safeZones = cfxZones.zonesWithProperty("scoreSafe")
|
||||
|
||||
@ -1,14 +1,15 @@
|
||||
cfxPlayerScoreUI = {}
|
||||
cfxPlayerScoreUI.version = "2.0.0"
|
||||
cfxPlayerScoreUI.version = "2.0.1"
|
||||
cfxPlayerScoreUI.verbose = false
|
||||
|
||||
--[[-- VERSION HISTORY
|
||||
- 1.0.2 - initial version
|
||||
- 1.0.3 - module check
|
||||
- 2.0.0 - removed cfxPlayer dependency, handles own commands
|
||||
- 2.0.1 - late start capability
|
||||
--]]--
|
||||
|
||||
cfxPlayerScoreUI.rootCommands = {} -- by unit's group name, for player aircraft
|
||||
cfxPlayerScoreUI.rootCommands = {} -- by unit's GROUP name, for player aircraft
|
||||
|
||||
-- redirect: avoid the debug environ of missionCommand
|
||||
function cfxPlayerScoreUI.redirectCommandX(args)
|
||||
@ -23,7 +24,7 @@ function cfxPlayerScoreUI.doCommandX(args)
|
||||
local gid = theGroup:getID()
|
||||
|
||||
if not cfxPlayerScore.scoreTextForPlayerNamed then
|
||||
trigger.action.outText("***pSGui: CANNOT FIND PlayerScore MODULE", 30)
|
||||
trigger.action.outText("***pSGUI: CANNOT FIND PlayerScore MODULE", 30)
|
||||
return
|
||||
end
|
||||
local desc = cfxPlayerScore.scoreTextForPlayerNamed(playerName)
|
||||
@ -35,10 +36,7 @@ end
|
||||
-- event handling: we are only interested in birth events
|
||||
-- for player aircraft
|
||||
--
|
||||
function cfxPlayerScoreUI:onEvent(event)
|
||||
if event.id ~= 15 then return end -- only birth
|
||||
if not event.initiator then return end -- no initiator, no joy
|
||||
local theUnit = event.initiator
|
||||
function cfxPlayerScore.processPlayerUnit(theUnit)
|
||||
if not theUnit.getPlayerName then return end -- no player name, bye!
|
||||
local playerName = theUnit:getPlayerName()
|
||||
if not playerName then return end
|
||||
@ -61,13 +59,7 @@ function cfxPlayerScoreUI:onEvent(event)
|
||||
-- we need to install a group menu item for scores.
|
||||
-- will persist through death
|
||||
local commandTxt = "Show Score / Kills"
|
||||
local theCommand = missionCommands.addCommandForGroup(
|
||||
gid,
|
||||
commandTxt,
|
||||
nil, -- root level
|
||||
cfxPlayerScoreUI.redirectCommandX,
|
||||
{groupName, playerName, "score"}
|
||||
)
|
||||
local theCommand = missionCommands.addCommandForGroup(gid, commandTxt, nil, cfxPlayerScoreUI.redirectCommandX, {groupName, playerName, "score"} )
|
||||
cfxPlayerScoreUI.rootCommands[groupName] = theCommand
|
||||
|
||||
if cfxPlayerScoreUI.verbose then
|
||||
@ -75,13 +67,21 @@ function cfxPlayerScoreUI:onEvent(event)
|
||||
end
|
||||
end
|
||||
|
||||
function cfxPlayerScoreUI:onEvent(event)
|
||||
if event.id ~= 15 then return end -- only birth
|
||||
if not event.initiator then return end -- no initiator, no joy
|
||||
local theUnit = event.initiator
|
||||
cfxPlayerScore.processPlayerUnit(theUnit)
|
||||
end
|
||||
|
||||
--
|
||||
-- Start
|
||||
--
|
||||
function cfxPlayerScoreUI.start()
|
||||
-- install the event handler for new player planes
|
||||
world.addEventHandler(cfxPlayerScoreUI)
|
||||
|
||||
-- process all existing players (late start)
|
||||
dcsCommon.iteratePlayers(cfxPlayerScore.processPlayerUnit)
|
||||
trigger.action.outText("cf/x cfxPlayerScoreUI v" .. cfxPlayerScoreUI.version .. " started", 30)
|
||||
return true
|
||||
end
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
cfxSmokeZone = {}
|
||||
cfxSmokeZone.version = "1.1.2"
|
||||
cfxSmokeZone.version = "1.1.3"
|
||||
cfxSmokeZone.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
"cfxZones", -- Zones, of course
|
||||
@ -18,6 +18,7 @@ cfxSmokeZone.requiredLibs = {
|
||||
1.1.0 - Watchflag upgrade
|
||||
1.1.1 - stopSmoke? input
|
||||
1.1.2 - 'agl', 'alt' synonymous for altitude to keep in line with fireFX
|
||||
1.1.3 - corrected smokeTriggerMethod in zone definition
|
||||
|
||||
--]]--
|
||||
cfxSmokeZone.smokeZones = {}
|
||||
@ -67,7 +68,7 @@ function cfxSmokeZone.processSmokeZone(aZone)
|
||||
aZone.smokeTriggerMethod = cfxZones.getStringFromZoneProperty(aZone, "triggerMethod", "change")
|
||||
|
||||
if cfxZones.hasProperty(aZone, "smokeTriggerMethod") then
|
||||
aZone.delayTriggerMethod = cfxZones.getStringFromZoneProperty(aZone, "smokeTriggerMethod", "change")
|
||||
aZone.smokeTriggerMethod = cfxZones.getStringFromZoneProperty(aZone, "smokeTriggerMethod", "change")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
cfxSpawnZones = {}
|
||||
cfxSpawnZones.version = "1.7.4"
|
||||
cfxSpawnZones.version = "1.7.5"
|
||||
cfxSpawnZones.requiredLibs = {
|
||||
"dcsCommon", -- common is of course needed for everything
|
||||
-- pretty stupid to check for this since we
|
||||
@ -66,6 +66,8 @@ cfxSpawnZones.spawnedGroups = {}
|
||||
1.7.2 - baseName now can can be set to zone name by issuing "*"
|
||||
1.7.3 - ability to hand off to delicates, useDelicates attribute
|
||||
1.7.4 - wait-attackZone fixes
|
||||
1.7.5 - improved verbosity on spawning
|
||||
- getRequestableSpawnersInRange() ignores height for distance
|
||||
|
||||
|
||||
- types - type strings, comma separated
|
||||
@ -204,6 +206,9 @@ function cfxSpawnZones.createSpawner(inZone)
|
||||
theSpawner.requestable = cfxZones.getBoolFromZoneProperty(inZone, "requestable", false)
|
||||
if theSpawner.requestable then
|
||||
theSpawner.paused = true
|
||||
if inZone.verbose or cfxSpawnZones.verbose then
|
||||
trigger.action.outText("+++spwn: spawner <" .. inZone.name .. "> paused: requestable enabled", 30)
|
||||
end
|
||||
end
|
||||
if cfxZones.hasProperty(inZone, "target") then
|
||||
theSpawner.target = cfxZones.getStringFromZoneProperty(inZone, "target", "")
|
||||
@ -248,7 +253,7 @@ function cfxSpawnZones.getRequestableSpawnersInRange(aPoint, aRange, aSide)
|
||||
for aZone, aSpawner in pairs(cfxSpawnZones.allSpawners) do
|
||||
-- iterate all zones and collect those that match
|
||||
local hasMatch = true
|
||||
local delta = dcsCommon.dist(aPoint, aZone.point)
|
||||
local delta = dcsCommon.distFlat(aPoint, cfxZones.getPoint(aZone))
|
||||
if delta>aRange then hasMatch = false end
|
||||
if aSide ~= 0 then
|
||||
-- check if side is correct for owned zone
|
||||
@ -333,9 +338,20 @@ function cfxSpawnZones.spawnWithSpawner(aSpawner)
|
||||
theCoalition,
|
||||
aSpawner.baseName .. "-" .. aSpawner.count, -- must be unique
|
||||
aSpawner.zone,
|
||||
unitTypes,
|
||||
unitTypes,
|
||||
aSpawner.formation,
|
||||
aSpawner.heading)
|
||||
if cfxSpawnZones.verbose or theZone.verbose then
|
||||
-- check created group size versus requested size
|
||||
trigger.action.outText("+++spwn: created <" .. theGroup:getSize() .. "> units, requested <" .. #unitTypes .. "> units, formation <" .. aSpawner.formation .. ">", 30)
|
||||
trigger.action.outText("+++spwn: zone <" .. theZone.name .. ">center at <" .. dcsCommon.point2text(p) .. ">", 30)
|
||||
local allUnits = theGroup:getUnits()
|
||||
for idx, myUnit in pairs (allUnits) do
|
||||
local pos = myUnit:getPoint()
|
||||
trigger.action.outText("unit <" .. myUnit:getName() .. "> at " .. dcsCommon.point2text(pos), 30)
|
||||
end
|
||||
end
|
||||
|
||||
aSpawner.theSpawn = theGroup
|
||||
aSpawner.count = aSpawner.count + 1
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
cfxZones = {}
|
||||
cfxZones.version = "3.0.8"
|
||||
cfxZones.version = "3.0.9"
|
||||
|
||||
-- cf/x zone management module
|
||||
-- reads dcs zones and makes them accessible and mutable
|
||||
@ -126,6 +126,7 @@ cfxZones.version = "3.0.8"
|
||||
- new createSimpleQuadZone()
|
||||
- 3.0.7 - getPoint() can also get land y when passing true as second param
|
||||
- 3.0.8 - new cfxZones.pointInOneOfZones(thePoint, zoneArray, useOrig)
|
||||
- 3.0.9 - new getFlareColorStringFromZoneProperty()
|
||||
|
||||
--]]--
|
||||
cfxZones.verbose = false
|
||||
@ -2241,6 +2242,30 @@ function cfxZones.getSmokeColorStringFromZoneProperty(theZone, theProperty, defa
|
||||
return default
|
||||
end
|
||||
|
||||
function cfxZones.getFlareColorStringFromZoneProperty(theZone, theProperty, default) -- smoke as 'red', 'green', or 1..5
|
||||
if not default then default = "red" end
|
||||
local s = cfxZones.getStringFromZoneProperty(theZone, theProperty, default)
|
||||
s = s:lower()
|
||||
s = dcsCommon.trim(s)
|
||||
-- check numbers
|
||||
if (s == "rnd") then return "random" end
|
||||
if (s == "0") then return "green" end
|
||||
if (s == "1") then return "red" end
|
||||
if (s == "2") then return "white" end
|
||||
if (s == "3") then return "yellow" end
|
||||
if (s == "-1") then return "random" end
|
||||
|
||||
if s == "green" or
|
||||
s == "red" or
|
||||
s == "white" or
|
||||
s == "yellow" or
|
||||
s == "random" then
|
||||
return s end
|
||||
|
||||
return default
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- Zone-based wildcard processing
|
||||
--
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
csarManager = {}
|
||||
csarManager.version = "2.2.5"
|
||||
csarManager.version = "2.2.6"
|
||||
csarManager.verbose = false
|
||||
csarManager.ups = 1
|
||||
|
||||
@ -63,6 +63,8 @@ csarManager.ups = 1
|
||||
- 2.2.4 - CSAR attribute value defaults name
|
||||
- start? attribute for CSAR as startCSAR? synonym
|
||||
- 2.2.5 - manual freq for CSAR was off by a factor of 10 - Corrected
|
||||
- 2.2.6 - useFlare, now also launches a flare in addition to smoke
|
||||
- zone testing uses getPoint for zones, supports moving csar zones
|
||||
|
||||
INTEGRATES AUTOMATICALLY WITH playerScore IF INSTALLED
|
||||
|
||||
@ -1028,6 +1030,18 @@ function csarManager.updateCSARMissions()
|
||||
csarManager.openMissions = newMissions -- this is the new batch
|
||||
end
|
||||
|
||||
function csarManager.launchFlare(args)
|
||||
local color = args.color
|
||||
if color < 0 then color = math.random(4) - 1 end
|
||||
local loc = args.loc -- with height
|
||||
if csarManager.verbose then
|
||||
trigger.action.outText("+++csarM: launching flare, c = " .. color .. " (" .. dcsCommon.flareColor2Text(color) .. ")", 30)
|
||||
end
|
||||
trigger.action.outTextForGroup(args.uID, "Launching flare!", 30)
|
||||
loc.y = loc.y + 3 -- launch 3 meters above ground
|
||||
trigger.action.signalFlare(loc, color, 0)
|
||||
end
|
||||
|
||||
function csarManager.update() -- every second
|
||||
-- schedule next invocation
|
||||
timer.scheduleFunction(csarManager.update, {}, timer.getTime() + 1/csarManager.ups)
|
||||
@ -1053,7 +1067,8 @@ function csarManager.update() -- every second
|
||||
-- enough to trigger comms
|
||||
for idx, csarMission in pairs (csarManager.openMissions) do
|
||||
-- check if we are inside trigger range on the same side
|
||||
local d = dcsCommon.distFlat(uPoint, csarMission.zone.point)
|
||||
local mp = cfxZones.getPoint(csarMission.zone, true)
|
||||
local d = dcsCommon.distFlat(uPoint, mp)
|
||||
if ((uSide == csarMission.side) or (csarMission.side == 0) )
|
||||
and (d < csarManager.rescueTriggerRange) then
|
||||
-- we are in trigger distance. if we did not notify before
|
||||
@ -1065,7 +1080,21 @@ function csarManager.update() -- every second
|
||||
local oclock = dcsCommon.clockPositionOfARelativeToB(csarMission.zone.point, uPoint, ownHeading) .. " o'clock"
|
||||
local msg = "\n" .. uName ..", " .. csarMission.name .. ". We can hear you, check your " .. oclock
|
||||
if csarManager.useSmoke then msg = msg .. " - popping smoke" end
|
||||
if csarManager.useFlare then
|
||||
if csarManager.useSmoke then
|
||||
msg = msg .. " and will launch flare in a few seconds"
|
||||
else
|
||||
msg = msg .. " - preparing flare"
|
||||
end
|
||||
-- schedule flare launch in 5-10 seconds
|
||||
local args = {}
|
||||
args.loc = mp
|
||||
args.color = csarManager.flareColor
|
||||
args.uID = uID
|
||||
timer.scheduleFunction(csarManager.launchFlare, args, timer.getTime() + math.random(5))
|
||||
end
|
||||
msg = msg .. "."
|
||||
|
||||
if csarMission.isHot then
|
||||
msg = msg .. " Be advised: LZ is hot."
|
||||
end
|
||||
@ -1394,6 +1423,10 @@ function csarManager.readConfigZone()
|
||||
csarManager.smokeColor = cfxZones.getSmokeColorStringFromZoneProperty(theZone, "smokeColor", "blue")
|
||||
csarManager.smokeColor = dcsCommon.smokeColor2Num(csarManager.smokeColor)
|
||||
|
||||
csarManager.useFlare = cfxZones.getBoolFromZoneProperty(theZone, "useFlare", true)
|
||||
csarManager.flareColor = cfxZones.getFlareColorStringFromZoneProperty(theZone, "flareColor", "red")
|
||||
csarManager.flareColor = dcsCommon.flareColor2Num(csarManager.flareColor)
|
||||
|
||||
|
||||
if cfxZones.hasProperty(theZone, "csarRedDelivered!") then
|
||||
csarManager.csarRedDelivered = cfxZones.getStringFromZoneProperty(theZone, "csarRedDelivered!", "*<none>")
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
dcsCommon = {}
|
||||
dcsCommon.version = "2.8.5"
|
||||
dcsCommon.version = "2.8.7"
|
||||
--[[-- VERSION HISTORY
|
||||
2.2.6 - compassPositionOfARelativeToB
|
||||
- clockPositionOfARelativeToB
|
||||
@ -147,7 +147,9 @@ dcsCommon.version = "2.8.5"
|
||||
2.8.5 - better guard in getGroupUnit()
|
||||
2.8.6 - phonetic helpers
|
||||
new spellString()
|
||||
|
||||
2.8.7 - new flareColor2Num()
|
||||
- new flareColor2Text()
|
||||
- new iteratePlayers()
|
||||
|
||||
--]]--
|
||||
|
||||
@ -2545,6 +2547,15 @@ end
|
||||
return ("unknown: " .. smokeColor)
|
||||
end
|
||||
|
||||
function dcsCommon.flareColor2Text(flareColor)
|
||||
if (flareColor == 0) then return "Green" end
|
||||
if (flareColor == 1) then return "Red" end
|
||||
if (flareColor == 2) then return "White" end
|
||||
if (flareColor == 3) then return "Yellow" end
|
||||
if (flareColor < 0) then return "Random" end
|
||||
return ("unknown: " .. flareColor)
|
||||
end
|
||||
|
||||
function dcsCommon.smokeColor2Num(smokeColor)
|
||||
if not smokeColor then smokeColor = "green" end
|
||||
if type(smokeColor) ~= "string" then return 0 end
|
||||
@ -2556,6 +2567,20 @@ end
|
||||
if (smokeColor == "blue") then return 4 end
|
||||
return 0
|
||||
end
|
||||
|
||||
function dcsCommon.flareColor2Num(flareColor)
|
||||
if not flareColor then flareColor = "green" end
|
||||
if type(flareColor) ~= "string" then return 0 end
|
||||
flareColor = flareColor:lower()
|
||||
if (flareColor == "green") then return 0 end
|
||||
if (flareColor == "red") then return 1 end
|
||||
if (flareColor == "white") then return 2 end
|
||||
if (flareColor == "yellow") then return 3 end
|
||||
if (flareColor == "random") then return -1 end
|
||||
if (flareColor == "rnd") then return -1 end
|
||||
return 0
|
||||
end
|
||||
|
||||
|
||||
function dcsCommon.markPointWithSmoke(p, smokeColor)
|
||||
if not smokeColor then smokeColor = 0 end
|
||||
@ -3315,6 +3340,23 @@ function dcsCommon.spellString(inString)
|
||||
return res
|
||||
end
|
||||
|
||||
--
|
||||
-- iterators
|
||||
--
|
||||
-- iteratePlayers - call callback for all player units
|
||||
-- callback is of signature callback(playerUnit)
|
||||
--
|
||||
|
||||
function dcsCommon.iteratePlayers(callBack)
|
||||
local factions = {0, 1, 2}
|
||||
for idx, theFaction in pairs(factions) do
|
||||
local players = coalition.getPlayers(theFaction)
|
||||
for idy, theUnit in pairs(players) do
|
||||
callBack(theUnit)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- SEMAPHORES
|
||||
--
|
||||
|
||||
116
modules/flareZone.lua
Normal file
116
modules/flareZone.lua
Normal file
@ -0,0 +1,116 @@
|
||||
flareZone = {}
|
||||
flareZone.version = "1.0.0"
|
||||
flareZone.verbose = false
|
||||
flareZone.name = "flareZone"
|
||||
|
||||
--[[-- VERSION HISTORY
|
||||
1.0.0 - initial version
|
||||
--]]--
|
||||
flareZone.requiredLibs = {
|
||||
"dcsCommon",
|
||||
"cfxZones",
|
||||
}
|
||||
flareZone.flares = {} -- all flare zones
|
||||
|
||||
function flareZone.addFlareZone(theZone)
|
||||
theZone.flareColor = cfxZones.getFlareColorStringFromZoneProperty(theZone, "flare", "green")
|
||||
theZone.flareColor = dcsCommon.flareColor2Num(theZone.flareColor)
|
||||
if cfxZones.hasProperty(theZone, "f?") then
|
||||
cfxZones.theZone.doFlare = cfxZones.getStringFromZoneProperty(theZone, "f?", "<none>")
|
||||
elseif cfxZones.hasProperty(theZone, "launchFlare?") then
|
||||
theZone.doFlare = cfxZones.getStringFromZoneProperty(theZone, "launchFlare?", "<none>")
|
||||
else
|
||||
theZone.doFlare = cfxZones.getStringFromZoneProperty(theZone, "launch?", "<none>")
|
||||
end
|
||||
theZone.lastDoFlare = trigger.misc.getUserFlag(theZone.doFlare)
|
||||
-- triggerMethod
|
||||
theZone.flareTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "triggerMethod", "change")
|
||||
if cfxZones.hasProperty(theZone, "flareTriggerMethod") then
|
||||
theZone.flareTriggerMethod = cfxZones.getStringFromZoneProperty(theZone, "flareTriggerMethod", "change")
|
||||
end
|
||||
|
||||
theZone.azimuthL, theZone.azimuthH = cfxZones.getPositiveRangeFromZoneProperty(theZone, "direction", 90) -- in degrees
|
||||
-- in DCS documentation, the parameter is incorrectly called 'azimuth'
|
||||
if cfxZones.hasProperty(theZone, "azimuth") then
|
||||
theZone.azimuthL, theZone.azimuthH = cfxZones.getPositiveRangeFromZoneProperty(theZone, "azimuth", 90) -- in degrees
|
||||
end
|
||||
-- theZone.azimuth = theZone.azimuth * 0.0174533 -- rads
|
||||
theZone.flareAlt = cfxZones.getNumberFromZoneProperty(theZone, "altitude", 1)
|
||||
if cfxZones.hasProperty(theZone, "alt") then
|
||||
theZone.flareAlt = cfxZones.getNumberFromZoneProperty(theZone, "alt", 1)
|
||||
elseif cfxZones.hasProperty(theZone, "flareAlt") then
|
||||
theZone.flareAlt = cfxZones.getNumberFromZoneProperty(theZone, "flareAlt", 1)
|
||||
elseif cfxZones.hasProperty(theZone, "agl") then
|
||||
theZone.flareAlt = cfxZones.getNumberFromZoneProperty(theZone, "agl", 1)
|
||||
end
|
||||
|
||||
theZone.salvoSizeL, theZone.salvoSizeH = cfxZones.getPositiveRangeFromZoneProperty(theZone, "salvo", 1)
|
||||
|
||||
theZone.salvoDurationL, theZone.salvoDurationH = cfxZones.getPositiveRangeFromZoneProperty(theZone, "duration", 1)
|
||||
|
||||
if theZone.verbose or flareZone.verbose then
|
||||
trigger.action.outText("+++flrZ: new flare <" .. theZone.name .. ">, color (" .. theZone.flareColor .. ")", 30)
|
||||
end
|
||||
table.insert(flareZone.flares, theZone)
|
||||
end
|
||||
|
||||
function flareZone.launch(theZone)
|
||||
local color = theZone.flareColor
|
||||
if color < 0 then color = math.random(4) - 1 end
|
||||
if flareZone.verbose or theZone.verbose then
|
||||
trigger.action.outText("+++flrZ: launching <" .. theZone.name .. ">, c = " .. color .. " (" .. dcsCommon.flareColor2Text(color) .. ")", 30)
|
||||
end
|
||||
local loc = cfxZones.getPoint(theZone, true) -- with height
|
||||
loc.y = loc.y + theZone.flareAlt
|
||||
-- calculate azimuth
|
||||
local azimuth = cfxZones.randomInRange(theZone.azimuthL, theZone.azimuthH) * 0.0174533 -- in rads
|
||||
trigger.action.signalFlare(loc, color, azimuth)
|
||||
end
|
||||
|
||||
function flareZone.update()
|
||||
-- call me again in a second
|
||||
timer.scheduleFunction(flareZone.update, {}, timer.getTime() + 1)
|
||||
|
||||
-- launch if flag banged
|
||||
for idx, theZone in pairs(flareZone.flares) do
|
||||
if cfxZones.testZoneFlag(theZone, theZone.doFlare, theZone.flareTriggerMethod, "lastDoFlare") then
|
||||
local salvo = cfxZones.randomInRange(theZone.salvoSizeL, theZone.salvoSizeH)
|
||||
if salvo < 2 then
|
||||
-- one-shot
|
||||
flareZone.launch(theZone)
|
||||
else
|
||||
-- pick a duration from range
|
||||
local duration = cfxZones.randomInRange(theZone.salvoDurationL, theZone.salvoDurationH)
|
||||
local duration = duration / salvo
|
||||
local d = 0
|
||||
for l=1, salvo do
|
||||
timer.scheduleFunction(flareZone.launch, theZone, timer.getTime() + d + 0.1)
|
||||
d = d + duration
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function flareZone.start()
|
||||
if not dcsCommon.libCheck("cfx Flare Zones", flareZone.requiredLibs) then return false end
|
||||
|
||||
-- collect all flares
|
||||
local attrZones = cfxZones.getZonesWithAttributeNamed("flare")
|
||||
for k, theZone in pairs(attrZones) do
|
||||
flareZone.addFlareZone(theZone) -- process attribute and add to zone
|
||||
end
|
||||
|
||||
-- start update
|
||||
flareZone.update() -- also starts all unpaused
|
||||
|
||||
-- say hi
|
||||
trigger.action.outText("cfx Flare Zone v" .. flareZone.version .. " started.", 30)
|
||||
return true
|
||||
end
|
||||
|
||||
-- let's go
|
||||
if not flareZone.start() then
|
||||
trigger.action.outText("cf/x Flare Zones aborted: missing libraries", 30)
|
||||
cfxSmokeZone = nil
|
||||
end
|
||||
158
modules/playerZone.lua
Normal file
158
modules/playerZone.lua
Normal file
@ -0,0 +1,158 @@
|
||||
playerZone = {}
|
||||
playerZone.version = "1.0.0"
|
||||
playerZone.requiredLibs = {
|
||||
"dcsCommon", -- always
|
||||
"cfxZones", -- Zones, of course
|
||||
}
|
||||
playerZone.playerZones = {}
|
||||
--[[--
|
||||
Version History
|
||||
1.0.0 - Initial version
|
||||
|
||||
--]]--
|
||||
|
||||
function playerZone.createPlayerZone(theZone)
|
||||
-- start val - a range
|
||||
theZone.pzCoalition = cfxZones.getCoalitionFromZoneProperty(theZone, "playerZone", 0)
|
||||
|
||||
|
||||
-- Method for outputs
|
||||
theZone.pzMethod = cfxZones.getStringFromZoneProperty(theZone, "method", "inc")
|
||||
if cfxZones.hasProperty(theZone, "pzMethod") then
|
||||
theZone.pzMethod = cfxZones.getStringFromZoneProperty(theZone, "pwMethod", "inc")
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "pNum") then
|
||||
theZone.pNum = cfxZones.getStringFromZoneProperty(theZone, "pNum", "none")
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "added!") then
|
||||
theZone.pAdd = cfxZones.getStringFromZoneProperty(theZone, "added!", "none")
|
||||
end
|
||||
|
||||
if cfxZones.hasProperty(theZone, "gone!") then
|
||||
theZone.pRemove = cfxZones.getStringFromZoneProperty(theZone, "gone!", "none")
|
||||
end
|
||||
|
||||
theZone.playersInZone = {} -- indexed by unit name
|
||||
end
|
||||
|
||||
|
||||
function playerZone.collectPlayersForZone(theZone)
|
||||
local factions = {0, 1, 2}
|
||||
local zonePlayers = {}
|
||||
for idx, f in pairs (factions) do
|
||||
if theZone.pzCoalition == 0 or f == theZone.pzCoalition then
|
||||
local allPlayers = coalition.getPlayers(f)
|
||||
for idy, theUnit in pairs (allPlayers) do
|
||||
local loc = theUnit:getPoint()
|
||||
if cfxZones.pointInZone(loc, theZone) then
|
||||
zonePlayers[theUnit:getName()] = theUnit
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return zonePlayers
|
||||
end
|
||||
|
||||
function playerZone.processZone(theZone)
|
||||
local nowInZone = playerZone.collectPlayersForZone(theZone)
|
||||
-- find new players in zone
|
||||
local hasNew = false
|
||||
local newCount = 0
|
||||
for name, theUnit in pairs(nowInZone) do
|
||||
if not theZone.playersInZone[name] then
|
||||
-- this unit was not here last time
|
||||
hasNew = true
|
||||
if playerZone.verbose or theZone.verbose then
|
||||
trigger.action.outText("+++pZone: new player unit <" .. name .. "> in zone <" .. theZone.name .. ">", 30)
|
||||
end
|
||||
end
|
||||
newCount = newCount + 1
|
||||
end
|
||||
-- find if players have left the zone
|
||||
local hasGone = false
|
||||
for name, theUnit in pairs(theZone.playersInZone) do
|
||||
if not nowInZone[name] then
|
||||
hasGone = true
|
||||
if playerZone.verbose or theZone.verbose then
|
||||
trigger.action.outText("+++pZone: player unit <" .. name .. "> disappeared from <" .. theZone.name .. ">", 30)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- flag handling and banging
|
||||
if theZone.pNum then
|
||||
cfxZones.setFlagValueMult(theZone.pNum, newCount, theZone)
|
||||
end
|
||||
|
||||
if theZone.pAdd and hasNew then
|
||||
if theZone.verbose or playerZone.verbose then
|
||||
trigger.action.outText("+++pZone: banging <" .. theZone.name .. ">'s 'added!' flags <" .. theZone.pAdd .. ">", 30)
|
||||
end
|
||||
cfxZones.pollFlag(theZone.pAdd, theZone.pzMethod, theZone)
|
||||
end
|
||||
|
||||
if theZone.pRemove and hasGone then
|
||||
if theZone.verbose or playerZone.verbose then
|
||||
trigger.action.outText("+++pZone: banging <" .. theZone.name .. ">'s 'gone' flags <" .. theZone.pRemove .. ">", 30)
|
||||
end
|
||||
cfxZones.pollFlag(theZone.pAdd, theZone.pzMethod, theZone)
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- Update
|
||||
--
|
||||
function playerZone.update()
|
||||
-- re-invoke in 1 second
|
||||
timer.scheduleFunction(playerZone.update, {}, timer.getTime() + 1)
|
||||
|
||||
-- iterate all zones and check them
|
||||
for idx, theZone in pairs(playerZone.playerZones) do
|
||||
playerZone.processZone(theZone)
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- Read Config Zone
|
||||
--
|
||||
function playerZone.readConfigZone(theZone)
|
||||
-- currently nothing to do
|
||||
end
|
||||
|
||||
--
|
||||
-- Start
|
||||
--
|
||||
function playerZone.start()
|
||||
if not dcsCommon.libCheck("cfx Player Zone",
|
||||
playerZone.requiredLibs)
|
||||
then return false end
|
||||
|
||||
local theZone = cfxZones.getZoneByName("playerZoneConfig")
|
||||
if not theZone then
|
||||
theZone = cfxZones.createSimpleZone("playerZoneConfig")
|
||||
end
|
||||
playerZone.readConfigZone(theZone)
|
||||
|
||||
local pZones = cfxZones.zonesWithProperty("playerZone")
|
||||
for k, aZone in pairs(pZones) do
|
||||
playerZone.createPlayerZone(aZone)
|
||||
playerZone.playerZones[aZone.name] = aZone
|
||||
end
|
||||
|
||||
-- start update cycle
|
||||
playerZone.update()
|
||||
return true
|
||||
end
|
||||
|
||||
if not playerZone.start() then
|
||||
trigger.action.outText("+++ aborted playerZone v" .. playerZone.version .. " -- start failed", 30)
|
||||
playerZone = nil
|
||||
end
|
||||
|
||||
--[[--
|
||||
additional features:
|
||||
- filter by type
|
||||
- filter by cat
|
||||
--]]--
|
||||
61
modules/shallows.lua
Normal file
61
modules/shallows.lua
Normal file
@ -0,0 +1,61 @@
|
||||
shallows = {}
|
||||
-- script to remove dead naval hulls that failed to sink
|
||||
-- once dead, smoke is put over the hull for 5 minutes
|
||||
-- and if still in game later, the hull is removed
|
||||
|
||||
shallows.version = "1.0.0"
|
||||
shallows.removeAfter = 5 -- minutes after kill event
|
||||
shallows.verbose = false
|
||||
shallows.uuid = 1
|
||||
|
||||
-- uuid
|
||||
function shallows.getUuid()
|
||||
shallows.uuid = shallows.uuid + 1
|
||||
return shallows.uuid
|
||||
end
|
||||
|
||||
-- remove hull
|
||||
function shallows.removeHull(args)
|
||||
if shallows.verbose then
|
||||
trigger.action.outText("enter remove hull for <" .. args.name .. ">", 30)
|
||||
end
|
||||
|
||||
-- remove smoke and whatever's left of ship
|
||||
trigger.action.effectSmokeStop(args.sName)
|
||||
Object.destroy(args.theUnit)
|
||||
if shallows.verbose then
|
||||
trigger.action.outText("Shallows: Removed <" .. args.name .. ">", 30)
|
||||
end
|
||||
end
|
||||
|
||||
-- watch the world turn and ships get killed
|
||||
function shallows:onEvent(event)
|
||||
if event.id ~= 28 then return end -- only kill events
|
||||
if not event.target then return end
|
||||
-- must be a ship
|
||||
local theUnit = event.target
|
||||
if not theUnit.getGroup or not theUnit:getGroup() then return end
|
||||
local theGroup = theUnit:getGroup()
|
||||
local cat = theGroup:getCategory()
|
||||
if cat ~= 3 then return end -- not a ship
|
||||
|
||||
if shallows.verbose then
|
||||
trigger.action.outText("Shallows: marking <" .. theUnit:getName() .. "> for deep-sixing", 30)
|
||||
end
|
||||
|
||||
-- mark it with smoke and fire
|
||||
local pos = theUnit:getPoint()
|
||||
local sName = theUnit:getName() .. shallows.getUuid()
|
||||
trigger.action.effectSmokeBig(pos, 2, 0.5, sName)
|
||||
|
||||
-- set timer to re-visit later
|
||||
local args = {}
|
||||
args.name = theUnit:getName()
|
||||
args.sName = sName
|
||||
args.theUnit = theUnit
|
||||
timer.scheduleFunction(shallows.removeHull, args, timer.getTime() + shallows.removeAfter * 60)
|
||||
end
|
||||
|
||||
-- start
|
||||
world.addEventHandler(shallows)
|
||||
trigger.action.outText("shallows " .. shallows.version .. " started", 30)
|
||||
@ -1,5 +1,5 @@
|
||||
williePete = {}
|
||||
williePete.version = "1.0.1"
|
||||
williePete.version = "1.0.2"
|
||||
williePete.ups = 10 -- we update at 10 fps, so accuracy of a
|
||||
-- missile moving at Mach 2 is within 33 meters,
|
||||
-- with interpolation even at 3 meters
|
||||
@ -12,7 +12,8 @@ williePete.requiredLibs = {
|
||||
--[[--
|
||||
Version History
|
||||
1.0.0 - Initial version
|
||||
1.0.1 - update to suppress verbosity
|
||||
1.0.1 - update to suppress verbosity
|
||||
1.0.2 - added Gazelle WP
|
||||
|
||||
--]]--
|
||||
|
||||
@ -23,7 +24,7 @@ williePete.blastedObjects = {} -- used when we detonate something
|
||||
|
||||
-- recognizes WP munitions. May require regular update when new
|
||||
-- models come out.
|
||||
williePete.smokeWeapons = {"HYDRA_70_M274","HYDRA_70_MK61","HYDRA_70_MK1","HYDRA_70_WTU1B","HYDRA_70_M156","HYDRA_70_M158","BDU_45B","BDU_33","BDU_45","BDU_45LGB","BDU_50HD","BDU_50LD","BDU_50LGB","C_8CM"}
|
||||
williePete.smokeWeapons = {"HYDRA_70_M274","HYDRA_70_MK61","HYDRA_70_MK1","HYDRA_70_WTU1B","HYDRA_70_M156","HYDRA_70_M158","BDU_45B","BDU_33","BDU_45","BDU_45LGB","BDU_50HD","BDU_50LD","BDU_50LGB","C_8CM", "SNEB_TYPE254_H1_GREEN", "SNEB_TYPE254_H1_RED", "SNEB_TYPE254_H1_YELLOW"}
|
||||
|
||||
function williePete.addWillie(theWillie)
|
||||
table.insert(williePete.willies, theWillie)
|
||||
|
||||
BIN
tutorial & demo missions/demo - Effects with a Flare.miz
Normal file
BIN
tutorial & demo missions/demo - Effects with a Flare.miz
Normal file
Binary file not shown.
BIN
tutorial & demo missions/demo - Not too shallow at all.miz
Normal file
BIN
tutorial & demo missions/demo - Not too shallow at all.miz
Normal file
Binary file not shown.
BIN
tutorial & demo missions/demo - Players in the Zone.miz
Normal file
BIN
tutorial & demo missions/demo - Players in the Zone.miz
Normal file
Binary file not shown.
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user