Added marker and voice alerts for allied/owned units and buildings
This commit is contained in:
parent
250d614338
commit
bca2153f53
|
@ -2,59 +2,286 @@ function widget:GetInfo()
|
|||
return {
|
||||
name = "Attack Warning",
|
||||
desc = "Warns if stuff gets attacked",
|
||||
author = "knorke",
|
||||
date = "Oct 2011",
|
||||
author = "knorke, Birdulon",
|
||||
date = "Oct 2011, June 2020",
|
||||
license = "GNU GPL, v2 or later",
|
||||
layer = 0,
|
||||
enabled = true,
|
||||
}
|
||||
end
|
||||
|
||||
local spGetSpectatingState = Spring.GetSpectatingState
|
||||
options_path = "Settings/Alerts"
|
||||
options_order = {
|
||||
'voiceSelfVolume', 'voiceAllyVolume', 'voiceSelfCooldown', 'voiceAllyCooldown',
|
||||
'markersEnable', 'markersSelfDuration', 'markersAllyDuration', 'markersSelfRadius', 'markersAllyRadius',
|
||||
}
|
||||
options = {
|
||||
markersEnable = {
|
||||
name = "Enable attack alert markers",
|
||||
type = "bool",
|
||||
value = true,
|
||||
desc = "Place temporary map markers to increase visibility of off-screen attacks",
|
||||
noHotkey = true,
|
||||
},
|
||||
markersSelfDuration = {
|
||||
name = "Attack marker duration (s) - self",
|
||||
type = "number",
|
||||
value = 5.0,
|
||||
min = 0.5,
|
||||
max = 15.0,
|
||||
step = 0.5,
|
||||
desc = "Attack markers for your own forces will be removed after this many seconds",
|
||||
noHotkey = true,
|
||||
},
|
||||
markersSelfRadius = {
|
||||
name = "Attack marker spacing (elmo) - self",
|
||||
type = "number",
|
||||
value = 750,
|
||||
min = 250,
|
||||
max = 2000,
|
||||
step = 250,
|
||||
desc = "If your forces are attacked within this distance of an existing attack marker, suppress the alert",
|
||||
noHotkey = true,
|
||||
},
|
||||
markersAllyDuration = {
|
||||
name = "Attack marker duration (s) - ally",
|
||||
type = "number",
|
||||
value = 2.5,
|
||||
min = 0.5,
|
||||
max = 15.0,
|
||||
step = 0.5,
|
||||
desc = "Attack markers for allied forces will be removed after this many seconds. You might want them to expire quicker than for your own forces.",
|
||||
noHotkey = true,
|
||||
},
|
||||
markersAllyRadius = {
|
||||
name = "Attack marker spacing (elmo) - ally",
|
||||
type = "number",
|
||||
value = 1500,
|
||||
min = 250,
|
||||
max = 3000,
|
||||
step = 250,
|
||||
desc = "If allied forces are attacked within this distance of an existing attack marker, suppress the alert. You might want them spaced out further than attacks on your forces.",
|
||||
noHotkey = true,
|
||||
},
|
||||
voiceSelfVolume = {
|
||||
name = "Attack alert volume - self",
|
||||
type = "number",
|
||||
value = 1.0,
|
||||
min = 0.0,
|
||||
max = 1.0,
|
||||
step = 0.05,
|
||||
desc = "Volume for attack alerts for your forces",
|
||||
noHotkey = true,
|
||||
},
|
||||
voiceSelfCooldown = {
|
||||
name = "Attack alert cooldown (s) - self",
|
||||
type = "number",
|
||||
value = 6,
|
||||
min = 1,
|
||||
max = 30,
|
||||
step = 1,
|
||||
desc = "Attack alerts for your forces will not be sounded if the last alert sound was within this period",
|
||||
noHotkey = true,
|
||||
},
|
||||
voiceAllyVolume = {
|
||||
name = "Attack alert volume - ally",
|
||||
type = "number",
|
||||
value = 1.0,
|
||||
min = 0.0,
|
||||
max = 1.0,
|
||||
step = 0.05,
|
||||
desc = "Volume for attack alerts for your forces",
|
||||
noHotkey = true,
|
||||
},
|
||||
voiceAllyCooldown = {
|
||||
name = "Attack alert cooldown (s) - ally",
|
||||
type = "number",
|
||||
value = 6,
|
||||
min = 1,
|
||||
max = 30,
|
||||
step = 1,
|
||||
desc = "Attack alerts for your forces will not be sounded if the last alert sound was within this period",
|
||||
noHotkey = true,
|
||||
},
|
||||
}
|
||||
|
||||
local warningDelay = 30 * 5 --in frames
|
||||
local lastWarning = 0 --in frames
|
||||
local localTeamID = Spring.GetLocalTeamID ()
|
||||
local voiceFilenameSelfBaseAttacked = "sounds/alerts/SelfBaseAttacked.ogg"
|
||||
local voiceFilenameSelfUnitAttacked = "sounds/alerts/SelfUnitAttacked.ogg"
|
||||
local voiceFilenameAllyBaseAttacked = "sounds/alerts/AllyBaseAttacked.ogg"
|
||||
local voiceFilenameAllyUnitAttacked = "sounds/alerts/AllyUnitAttacked.ogg"
|
||||
local voiceFilenames = { -- [bSelf][isBuilding]
|
||||
[false]={
|
||||
[false]=voiceFilenameAllyUnitAttacked,
|
||||
[true]=voiceFilenameAllyBaseAttacked,
|
||||
},
|
||||
[true]={
|
||||
[false]=voiceFilenameSelfUnitAttacked,
|
||||
[true]=voiceFilenameSelfBaseAttacked,
|
||||
},
|
||||
}
|
||||
|
||||
local osClock = os.clock
|
||||
local spPlaySoundFile = Spring.PlaySoundFile -- (filename, volume, posx, posy, posz, speedx, speedy, speedz, channel)
|
||||
local spMarkerAddPoint = Spring.MarkerAddPoint -- (x, y, z, text, localonly)
|
||||
local spMarkerErasePosition = Spring.MarkerErasePosition -- (x, y, z) Sadly you can't keep handles of markers
|
||||
local spGetUnitPosition = Spring.GetUnitPosition
|
||||
local spAreTeamsAllied = Spring.AreTeamsAllied
|
||||
local spGetGameFrame = Spring.GetGameFrame
|
||||
local spGetSpectatingState = Spring.GetSpectatingState
|
||||
local spEcho = Spring.Echo
|
||||
local spSetLastMessagePosition = Spring.SetLastMessagePosition
|
||||
|
||||
local warningDelay = 30 * 5 --in frames
|
||||
local lastWarning = 0 --in frames
|
||||
local localTeamID = Spring.GetLocalTeamID()
|
||||
|
||||
local under_attack_translation
|
||||
local function languageChanged ()
|
||||
under_attack_translation = WG.Translate ("interface", "unit_under_attack")
|
||||
local function languageChanged()
|
||||
under_attack_translation = WG.Translate("interface", "unit_under_attack")
|
||||
end
|
||||
|
||||
function widget:UnitDamaged (unitID, unitDefID, unitTeam, damage, paralyzer, weaponID, attackerID, attackerDefID, attackerTeam)
|
||||
if damage <= 0 then return end
|
||||
local currentFrame = Spring.GetGameFrame ()
|
||||
if (lastWarning+warningDelay > currentFrame) then
|
||||
return
|
||||
local function dist2(x1, z1, x2, z2)
|
||||
-- Distance squared between two points
|
||||
-- Coords are X and Z because Spring uses Y as the vertical axis, and we only care about the planar position.
|
||||
local dx, dz = x2-x1, z2-z1
|
||||
return dx*dx + dz*dz
|
||||
end
|
||||
|
||||
function TextAlert(unitDefID, x, y, z)
|
||||
local currentFrame = spGetGameFrame()
|
||||
if (lastWarning+warningDelay > currentFrame) then return end
|
||||
lastWarning = currentFrame
|
||||
spEcho("game_message: " .. Spring.Utilities.GetHumanName(UnitDefs[unitDefID]) .. " " .. under_attack_translation)
|
||||
spSetLastMessagePosition(x, y, z)
|
||||
end
|
||||
|
||||
local attackMarkers = {}
|
||||
local tLastVoice = 0
|
||||
local markerOffset = 2 -- Offset the marker Y coord by this much to avoid potential conflict with user-placed markers
|
||||
|
||||
local data = { -- [bSelf]
|
||||
[false]={
|
||||
voiceCooldown=options.voiceAllyCooldown.value,
|
||||
voiceVolume=options.voiceAllyVolume.value,
|
||||
markerD2=options.markersAllyRadius.value*options.markersAllyRadius.value,
|
||||
markerPrefix={[false]="Allied Unit", [true]="Allied Base"},
|
||||
markerDuration=options.markersAllyDuration.value,
|
||||
},
|
||||
[true]={
|
||||
voiceCooldown=options.voiceSelfCooldown.value,
|
||||
voiceVolume=options.voiceSelfVolume.value,
|
||||
markerD2=options.markersSelfRadius.value*options.markersSelfRadius.value,
|
||||
markerPrefix={[false]="Unit", [true]="Base"},
|
||||
markerDuration=options.markersSelfDuration.value,
|
||||
},
|
||||
}
|
||||
function UpdateOptions()
|
||||
data = { -- [bSelf]
|
||||
[false]={
|
||||
voiceCooldown=options.voiceAllyCooldown.value,
|
||||
voiceVolume=options.voiceAllyVolume.value,
|
||||
markerD2=options.markersAllyRadius.value*options.markersAllyRadius.value,
|
||||
markerPrefix={[false]="Allied Unit", [true]="Allied Base"},
|
||||
markerDuration=options.markersAllyDuration.value,
|
||||
},
|
||||
[true]={
|
||||
voiceCooldown=options.voiceSelfCooldown.value,
|
||||
voiceVolume=options.voiceSelfVolume.value,
|
||||
markerD2=options.markersSelfRadius.value*options.markersSelfRadius.value,
|
||||
markerPrefix={[false]="Unit", [true]="Base"},
|
||||
markerDuration=options.markersSelfDuration.value,
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
function MarkerAlert(unitTeam, unitDefID, x, y, z)
|
||||
-- Assumptions for calling this function:
|
||||
-- - Unit is allied to the player
|
||||
-- - Unit is not on-screen for the player
|
||||
y = y + markerOffset
|
||||
local t = osClock()
|
||||
local isBuilding = UnitDefs[unitDefID].isBuilding
|
||||
local bSelf = (unitTeam == localTeamID)
|
||||
|
||||
-- Do voice alert if appropriate
|
||||
if t > (tLastVoice + data[bSelf].voiceCooldown) then
|
||||
spPlaySoundFile(voiceFilenames[bSelf][isBuilding], data[bSelf].voiceVolume, nil, nil, nil, nil, nil, nil, "userinterface")
|
||||
spSetLastMessagePosition(x, y, z)
|
||||
tLastVoice = t
|
||||
end
|
||||
if (localTeamID==unitTeam and not Spring.IsUnitInView (unitID)) then
|
||||
lastWarning = currentFrame
|
||||
Spring.Echo ("game_message: " .. Spring.Utilities.GetHumanName(UnitDefs[unitDefID]) .. " " .. under_attack_translation)
|
||||
--Spring.PlaySoundFile (blabla attack.wav, ... "userinterface")
|
||||
local x,y,z = Spring.GetUnitPosition (unitID)
|
||||
if (x and y and z) then
|
||||
Spring.SetLastMessagePosition (x,y,z)
|
||||
|
||||
-- Check if the new marker would be too close to any existing markers
|
||||
for i=1,#attackMarkers do
|
||||
local marker = attackMarkers[i]
|
||||
local d2 = dist2(x, z, marker.x, marker.z)
|
||||
if d2 < data[bSelf].markerD2 then return end
|
||||
end
|
||||
-- Place a marker
|
||||
attackMarkers[#attackMarkers+1] = {x=x, y=y, z=z, tDeath=t+data[bSelf].markerDuration}
|
||||
spMarkerAddPoint(x, y, z, data[bSelf].markerPrefix[isBuilding] .. " attacked", true)
|
||||
spSetLastMessagePosition(x, y, z)
|
||||
end
|
||||
|
||||
|
||||
function CleanMarkers()
|
||||
local t = osClock()
|
||||
for i=#attackMarkers,1,-1 do
|
||||
local m = attackMarkers[i]
|
||||
if m.tDeath < t then
|
||||
spMarkerErasePosition(m.x, m.y, m.z)
|
||||
table.remove(attackMarkers, i)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local UPDATE_PERIOD = 0.25 -- Check to remove markers every 250ms
|
||||
local timeframetimer = 0.0
|
||||
function widget:Update(dt)
|
||||
timeframetimer = timeframetimer + dt
|
||||
if (timeframetimer > UPDATE_PERIOD) then
|
||||
UpdateOptions() -- Couldn't find a suitable call-in for options being changed
|
||||
CleanMarkers()
|
||||
timeframetimer = 0
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function widget:UnitDamaged(unitID, unitDefID, unitTeam, damage, paralyzer, weaponID, attackerID, attackerDefID, attackerTeam)
|
||||
if damage <= 0 then return end
|
||||
--spEcho("game_message: Processing damage event! " .. tostring(unitID) .. ", " .. tostring(unitDefID) .. ", " .. tostring(unitTeam) .. ", " .. tostring(attackerTeam) .. ", " .. tostring(attackerID));
|
||||
if Spring.IsUnitInView(unitID) then return end
|
||||
if not spAreTeamsAllied(localTeamID, unitTeam) then return end
|
||||
if not unitTeam then return end -- I don't even?
|
||||
if not unitDefID then return end -- Haven't encountered this being nil but I'm not risking it
|
||||
if attackerTeam and spAreTeamsAllied(unitTeam, attackerTeam) then return end -- Get a lot of nil for attacker stats, probably from things out of vision/radar.
|
||||
local x,y,z = spGetUnitPosition(unitID)
|
||||
if not (x and y and z) then return end
|
||||
TextAlert(unitDefID, x, y, z)
|
||||
MarkerAlert(unitTeam, unitDefID, x, y, z)
|
||||
end
|
||||
|
||||
|
||||
function widget:Initialize()
|
||||
if spGetSpectatingState() then
|
||||
widgetHandler:RemoveWidget()
|
||||
end
|
||||
|
||||
WG.InitializeTranslation (languageChanged, GetInfo().name)
|
||||
WG.InitializeTranslation(languageChanged, GetInfo().name)
|
||||
end
|
||||
|
||||
|
||||
function widget:Shutdown()
|
||||
WG.ShutdownTranslation(GetInfo().name)
|
||||
end
|
||||
|
||||
|
||||
--changing teams, rejoin, becoming spec etc
|
||||
function widget:PlayerChanged (playerID)
|
||||
function widget:PlayerChanged(playerID)
|
||||
if spGetSpectatingState() then
|
||||
--Spring.Echo("<Attack Warning>: Spectator mode. Widget removed.")
|
||||
widgetHandler:RemoveWidget()
|
||||
end
|
||||
localTeamID = Spring.GetLocalTeamID ()
|
||||
localTeamID = Spring.GetLocalTeamID()
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue