diff --git a/LuaRules/Gadgets/cus_gl4.lua b/LuaRules/Gadgets/cus_gl4.lua index d854d6de7f..37185dd4e9 100644 --- a/LuaRules/Gadgets/cus_gl4.lua +++ b/LuaRules/Gadgets/cus_gl4.lua @@ -2080,7 +2080,7 @@ function gadget:UnitCreated(unitID, unitDefID) unitDefModelMaxY[unitDefID] = (ud.customParams.build_effect_sweep_height and tonumber(ud.customParams.build_effect_sweep_height) or ud.model.maxy or 10) end uniformCache[1] = unitDefModelMaxY[unitDefID] - gl.SetUnitBufferUniforms(unitID, uniformCache, 11) -- set unit height + gl.SetUnitBufferUniforms(unitID, uniformCache, 15) -- set unit height (moved off slot 11 -> ability slot; 15 is outside the updater block write 1-11, so not clobbered) uniformCache[1] = 0 gl.SetUnitBufferUniforms(unitID, uniformCache, 12) -- clear cloak effect gl.SetUnitBufferUniforms(unitID, uniformCache, 6) -- clear selectedness effect diff --git a/LuaRules/Gadgets/unit_healthbars_widget_forwarding.lua b/LuaRules/Gadgets/unit_healthbars_widget_forwarding.lua new file mode 100644 index 0000000000..454523ef1d --- /dev/null +++ b/LuaRules/Gadgets/unit_healthbars_widget_forwarding.lua @@ -0,0 +1,193 @@ +function gadget:GetInfo() + return { + name = "Healthbars Widget Forwarding", + desc = "Notifies widgets that a feature reclaim or resurrect action has begun, updates GL Uniforms, and also notifies on capture start, emp damage, reload", + author = "Beherith", -- ty Sprung + date = "2021.11.25", + license = "GNU GPL, v2 or later", + layer = -1, + enabled = true + } +end + + +if gadgetHandler:IsSyncedCode() then + + local forwardedFeatureIDs = {} -- so we only forward the start event once + local forwardedCaptureUnitIDs = {} + local weapondefsreload = {} + local unitreloadframe = {} -- maps unitID to next frame it can shoot its primary weapon, cause lasers retrigger projetileCreated every frame + local minReloadTime = 5 -- in concerto with healthbars widget + + function gadget:AllowFeatureBuildStep(builderID, builderTeam, featureID, featureDefID, step) + -- VERY IMPORTANT: This also gets called on resurrect!, but its very hard to tell if its a reclaim, but we can make the mother of all assumptions: + -- features die at 100% metal value + -- step is negative if 'reclaiming' + -- step is large positive if refilling + -- step is small positive if rezzing + + local gf = Spring.GetGameFrame() + --Spring.Echo("AllowFeatureBuildStep",gf,builderID, builderTeam, featureID, featureDefID, step) + if forwardedFeatureIDs[featureID] == nil or forwardedFeatureIDs[featureID] < gf then + forwardedFeatureIDs[featureID] = gf + SendToUnsynced("featureReclaimFrame", featureID, step) + end + return true + end + + function gadget:AllowUnitCaptureStep(builderID, builderTeam, unitID, unitDefID, part) + if forwardedCaptureUnitIDs[unitID] == nil then + forwardedCaptureUnitIDs[unitID] = true + SendToUnsynced("unitCaptureFrame", unitID, part) + end + return true + end + + function gadget:FeatureDestroyed(featureID, allyTeamID) + forwardedFeatureIDs[featureID] = nil + end + + function gadget:UnitDestroyed(unitID) + forwardedCaptureUnitIDs[unitID] = nil + unitreloadframe[unitID] = nil + end + + function gadget:UnitTaken(unitID) + forwardedCaptureUnitIDs[unitID] = nil + end + + function gadget:Initialize() + for udefID, unitDef in pairs(UnitDefs) do + local weapons = unitDef.weapons + local watchweaponID = nil + local longestreloadtime = -1 + local longestreloadindex + for i = 1, #weapons do + local WeaponDefID = weapons[i].weaponDef + local WeaponDef = WeaponDefs[WeaponDefID] + if WeaponDef.reload and WeaponDef.reload >0 and WeaponDef.reload >= longestreloadtime then + longestreloadtime = WeaponDef.reload + watchweaponID = WeaponDefID + longestreloadindex = i + end + end + if watchweaponID and longestreloadtime > minReloadTime then + --Spring.Echo("Unit with watched reload time:", unitDef.name, longestreloadtime, watchweaponID, udefID) + weapondefsreload[watchweaponID] = longestreloadindex + Script.SetWatchProjectile(watchweaponID, true) + end + end + end + + function gadget:ProjectileCreated(projectileID, ownerID, weaponID) -- needs: Script.SetWatchProjectile(weaponDefID, true) + --local unitDef = Spring.GetUnitDefID(ownerID) + --Spring.Echo("gadget:ProjectileCreated(",projectileID, ownerID, weaponID,weapondefsreload[weaponID],unitreloadframe[ownerID], ")") + local weaponIndex = weapondefsreload[weaponID] + + if weaponIndex then + local gf = Spring.GetGameFrame() + local reloadFrame = Spring.GetUnitWeaponState(ownerID, weaponIndex, 'reloadFrame') + + if unitreloadframe[ownerID] == nil or unitreloadframe[ownerID] <= gf then + SendToUnsynced("projetileCreatedReload", projectileID, ownerID, weaponID) + unitreloadframe[ownerID] = reloadFrame + end + end + end + +else + + local glSetFeatureBufferUniforms = gl.SetFeatureBufferUniforms + local GetFeatureResources = Spring.GetFeatureResources + local rezreclaim = {0.0, 1.0} -- this is just a small table cache, so we dont allocate a new table for every update + local forwardedFeatureIDsResurrect = {} -- so we only forward the start event once + local forwardedFeatureIDsReclaim = {} -- so we only forward the start event once + local myTeamID = Spring.GetMyTeamID() + local myAllyTeamID = Spring.GetMyAllyTeamID() + local _, fullview = Spring.GetSpectatingState() + local IsUnitInLos = Spring.IsUnitInLos + local GetFeatureHealth = Spring.GetFeatureHealth + local headless = false + + function gadget:PlayerChanged(playerID) + myTeamID = Spring.GetMyTeamID() + myAllyTeamID = Spring.GetMyAllyTeamID() + _, fullview = Spring.GetSpectatingState() + end + + function featureReclaimFrame(cmd, featureID, step) + --Spring.Echo("HandleFeatureReclaimStarted", featureID) + rezreclaim[1] = select(3, GetFeatureHealth( featureID )) -- resurrect progress + rezreclaim[2] = select(5, GetFeatureResources(featureID)) -- reclaim percent + + --Spring.Echo('rezreclaim', rezreclaim[1], rezreclaim[2]) + + --if not headless then glSetFeatureBufferUniforms(featureID, rezreclaim, 1) end -- update GL, at offset of 1 + + if step > 0 and forwardedFeatureIDsResurrect[featureID] == nil and Script.LuaUI("FeatureReclaimStartedHealthbars") then + forwardedFeatureIDsResurrect[featureID] = true + --Spring.Echo("HandleFeatureReclaimStartedHealthbars", featureID, step) + Script.LuaUI.FeatureReclaimStartedHealthbars(featureID, step) + end + + if step < 0 and forwardedFeatureIDsReclaim[featureID] == nil and Script.LuaUI("FeatureReclaimStartedHealthbars") then + forwardedFeatureIDsReclaim[featureID] = true + --Spring.Echo("HandleFeatureReclaimStartedHealthbars", featureID, step) + Script.LuaUI.FeatureReclaimStartedHealthbars(featureID, step) + end + end + + function unitCaptureFrame(cmd, unitID, step) + if not fullview and not IsUnitInLos(unitID, myAllyTeamID) then return end + if Script.LuaUI("UnitCaptureStartedHealthbars") then + --Spring.Echo("UnitCaptureStartedHealthbars", unitID, step) + Script.LuaUI.UnitCaptureStartedHealthbars(unitID, step) + end + end + + function projetileCreatedReload(cmd, projectileID, ownerID, weaponID) + --Spring.Echo("unsynced projetileCreatedReload", projectileID, ownerID, weaponID, fullview, Spring.GetUnitTeam(ownerID)) + if fullview or Spring.GetUnitTeam(ownerID) == myTeamID then + if Script.LuaUI("ProjectileCreatedReloadHB") then + --Spring.Echo("G:ProjectileCreatedReloadHB", projectileID, ownerID, weaponID) + Script.LuaUI.ProjectileCreatedReloadHB(projectileID, ownerID, weaponID) + end + end + end + + function gadget:FeatureDestroyed(featureID, allyTeamID) + forwardedFeatureIDsResurrect[featureID] = nil + forwardedFeatureIDsReclaim[featureID] = nil + end + + function gadget:UnitDamaged(unitID, unitDefID, unitTeam, damage, paralyzer) + --Spring.Echo("gadget:UnitDamaged",unitID, unitDefID, unitTeam, damage, paralyzer) + if paralyzer then + if not fullview and not IsUnitInLos(unitID, myAllyTeamID) then return end + + if damage > 0 then + if Script.LuaUI("UnitParalyzeDamageHealthbars") then + --Spring.Echo("UnitParalyzeDamageHealthbars", unitID, step) + Script.LuaUI.UnitParalyzeDamageHealthbars(unitID, unitDefID, damage) + end + if Script.LuaUI("UnitParalyzeDamageEffect") then + --Spring.Echo("UnitParalyzeDamageHealthbars", unitID, step) + Script.LuaUI.UnitParalyzeDamageEffect(unitID, unitDefID, damage) + end + end + end + end + + function gadget:Initialize() + headless = Spring.GetConfigInt("Headless", 0) > 0 + gadgetHandler:AddSyncAction("featureReclaimFrame", featureReclaimFrame) + gadgetHandler:AddSyncAction("unitCaptureFrame", unitCaptureFrame) + gadgetHandler:AddSyncAction("projetileCreatedReload", projetileCreatedReload) + end + + function gadget:ShutDown() + gadgetHandler:RemoveSyncAction("featureReclaimFrame") + gadgetHandler:RemoveSyncAction("unitCaptureFrame") + gadgetHandler:RemoveSyncAction("projetileCreatedReload") + end +end diff --git a/LuaUI/Images/bars/build.png b/LuaUI/Images/bars/build.png new file mode 100644 index 0000000000..d5b642be7f Binary files /dev/null and b/LuaUI/Images/bars/build.png differ diff --git a/LuaUI/Images/bars/capture.png b/LuaUI/Images/bars/capture.png new file mode 100644 index 0000000000..e80d318449 Binary files /dev/null and b/LuaUI/Images/bars/capture.png differ diff --git a/LuaUI/Images/bars/disable.png b/LuaUI/Images/bars/disable.png new file mode 100644 index 0000000000..40d49edc77 Binary files /dev/null and b/LuaUI/Images/bars/disable.png differ diff --git a/LuaUI/Images/bars/disarm.png b/LuaUI/Images/bars/disarm.png new file mode 100644 index 0000000000..dd3f5171dd Binary files /dev/null and b/LuaUI/Images/bars/disarm.png differ diff --git a/LuaUI/Images/bars/health.png b/LuaUI/Images/bars/health.png new file mode 100644 index 0000000000..ae3fecc68a Binary files /dev/null and b/LuaUI/Images/bars/health.png differ diff --git a/LuaUI/Images/bars/reclaim.png b/LuaUI/Images/bars/reclaim.png new file mode 100644 index 0000000000..ee2b1da836 Binary files /dev/null and b/LuaUI/Images/bars/reclaim.png differ diff --git a/LuaUI/Images/bars/resurrect.png b/LuaUI/Images/bars/resurrect.png new file mode 100644 index 0000000000..fc60af7e92 Binary files /dev/null and b/LuaUI/Images/bars/resurrect.png differ diff --git a/LuaUI/Images/bars/shield.png b/LuaUI/Images/bars/shield.png new file mode 100644 index 0000000000..121b074f14 Binary files /dev/null and b/LuaUI/Images/bars/shield.png differ diff --git a/LuaUI/Images/bars/slow.png b/LuaUI/Images/bars/slow.png new file mode 100644 index 0000000000..8446fcce37 Binary files /dev/null and b/LuaUI/Images/bars/slow.png differ diff --git a/LuaUI/Images/commands/Bold/captureReload.png b/LuaUI/Images/commands/Bold/captureReload.png new file mode 100644 index 0000000000..389635bea3 Binary files /dev/null and b/LuaUI/Images/commands/Bold/captureReload.png differ diff --git a/LuaUI/Images/commands/Bold/disable.png b/LuaUI/Images/commands/Bold/disable.png new file mode 100644 index 0000000000..6aff10e989 Binary files /dev/null and b/LuaUI/Images/commands/Bold/disable.png differ diff --git a/LuaUI/Images/commands/Bold/disarm.png b/LuaUI/Images/commands/Bold/disarm.png new file mode 100644 index 0000000000..101109f5db Binary files /dev/null and b/LuaUI/Images/commands/Bold/disarm.png differ diff --git a/LuaUI/Images/commands/Bold/slow.png b/LuaUI/Images/commands/Bold/slow.png new file mode 100644 index 0000000000..2ed515f451 Binary files /dev/null and b/LuaUI/Images/commands/Bold/slow.png differ diff --git a/LuaUI/Images/numbers.png b/LuaUI/Images/numbers.png new file mode 100644 index 0000000000..e49acbb019 Binary files /dev/null and b/LuaUI/Images/numbers.png differ diff --git a/LuaUI/Widgets/Include/gl_uniform_channels.lua b/LuaUI/Widgets/Include/gl_uniform_channels.lua new file mode 100644 index 0000000000..6754b9554b --- /dev/null +++ b/LuaUI/Widgets/Include/gl_uniform_channels.lua @@ -0,0 +1,406 @@ + +----------------------------------------------------------------- +-- Units +----------------------------------------------------------------- + +local gameSpeed = Game.gameSpeed + +local minReloadTime = 0.1 -- floor for picking a unit's primary (slowest) weapon; the visible "show timer" cutoff is the reloadThreshold option in the widget + +unitParalyzeChannel = 1 +unitDisarmChannel = 2 +unitSlowChannel = 3 +-- slot 4 reserved: gfx_paralyze_effect writes combined para/disarm/slow/fire bitmask here +unitShieldChannel = 5 +unitSelectednessChannel = 6 -- highlight/selectedness; pushed via WG.SetUnitHighlight and folded into the updater's block write (cus reads userDefined[1].z) +unitBuildChannel = 7 +unitGooChannel = 8 -- Goo / Morph (mutually exclusive per unit) +unitMorphChannel = 8 +unitPrimaryReloadChannel = 9 -- primary weapon / script reload / captureReload / reammo (mutually exclusive per unit) +unitPrimaryCountChannel = 10 -- burst ammo missing count (0 = no bar for non-burst units) +unitSecondaryReloadChannel = 11 -- dgun / teleport / heat / speed / stockpile (mutually exclusive per unit) +unitSecondaryCountChannel = 12 -- stockpile / burst-secondary missing count (0 = no bar) +unitCaptureChannel = 13 +unitMovementChannel = 14 -- jump / movement-type ability / movement-type dgun (mutually exclusive per unit) +unitStateCountChannel = 15 -- packed: state-count bits 0-3 (overlay row centering) + isSelected bit 4; pushed via WG.SetUnitStateCount / WG.SetUnitSelected +unitHealthChannel = 20 -- reads engine-native health/maxHealth from the uniform buffer + +-- Bit-pack descriptor: channel id -> {float, bitOffset, bitWidth}. width 0 = whole float (passthrough = +-- current behavior). Single source of truth for the updater's packing and the shader's readField (the +-- shader's PACK_* const arrays are generated from this via buildChannelPackDefines). Channels migrate to +-- packed slots one at a time; foundation = all passthrough (float = channel, width 0), zero behavior change. +-- entry = {float, bitOffset, bitWidth, type}. type: 0 = raw passthrough, 1 = status (12-bit int decoded +-- to the old <=1 magnitude / >1 = 1+seconds semantics so downstream consumers are unchanged). +channelPack = {} +for c = 0, 15 do channelPack[c] = { c, 0, 0, 0 } end +channelPack[unitParalyzeChannel] = { 1, 0, 12, 1 } -- float 1 low, status +channelPack[unitDisarmChannel] = { 1, 12, 12, 1 } -- float 1 high, status +channelPack[unitSlowChannel] = { 2, 0, 12, 1 } -- float 2 low, status +channelPack[unitCaptureChannel] = { 2, 12, 7, 2 } -- float 2, percent (0-100 -> 0-1); frees floats 3 & 13 +-- shield (gauge) and morph/goo (frame-based duration, rate-measured ETA like build) are ABILITIES -> they +-- go through the ability-slot step, not fixed channels. Left passthrough here. +-- Ability slots: 5 generic 12-bit slots. Reuse the old reload/count/movement channel ids (9/10/11/12/14) +-- as the slot ids; remap them to physical floats 9,10,11 (2 slots per float, slot 5 alone in 11). raw +-- extraction -- the bar's BARTYPE (gauge/modular/count) drives the decode, since the type is per-unit. +abilitySlotChannel = { 9, 10, 11, 12, 14 } +channelPack[9] = { 9, 0, 12, 0 } -- slot 1: float 9 low +channelPack[10] = { 9, 12, 12, 0 } -- slot 2: float 9 high +channelPack[11] = { 10, 0, 12, 0 } -- slot 3: float 10 low +channelPack[12] = { 10, 12, 12, 0 } -- slot 4: float 10 high +channelPack[14] = { 11, 0, 12, 0 } -- slot 5: float 11 low + +function buildChannelPackDefines() + local f, o, w, t = {}, {}, {}, {} + for c = 0, 15 do + f[#f + 1] = channelPack[c][1] + o[#o + 1] = channelPack[c][2] + w[#w + 1] = channelPack[c][3] + t[#t + 1] = channelPack[c][4] + end + return table.concat(f, ","), table.concat(o, ","), table.concat(w, ","), table.concat(t, ",") +end + +----------------------------------------------------------------- +-- Features +----------------------------------------------------------------- + +featureHealthChannel = 1 +featureResurrectChannel = 2 +featureReclaimChannel = 3 + +unitDefIgnore = {} -- commanders! +unitDefHasShield = {} -- value is shield max power +unitDefCanStockpile = {} -- 0/1? +unitDefPrimaryReload = {} -- value is max reload time +unitDefHeights = {} -- maps unitDefs to height +unitDefPrimaryWeapon = {} -- the index for reloadable weapon on unitdef weapons (slowest = primary) +unitDefWeapons = {} -- ordered list {index, reload, class, color} of distinct reloadable weapons, slowest first +unitDefExtraWeapons = {} -- for pure multi-weapon units: weapons 2..4 assigned to extra ability channels +unitDefHasAbility = {} +unitDefAbilityIsMovement = {} -- true if unit's ability is movement-type (e.g., Swift's Sprint) +unitDefScriptReload = {} +unitDefBurstCount = {} -- for units with script_burst customParam (Picket, Hacksaw, etc.) +unitDefDgun = {} +unitDefDgunReload = {} +unitDefDgunIsMovement = {} -- true if unit's dgun is movement-type (goes to ch6 instead of ch11) +unitDefHasGoo = {} +unitDefGooFrames = {} -- nominal (full-metal) frames to replicate, for the goo gauge's smooth countdown +unitDefHasJump = {} -- number of jump charges (>=1; truthy when the unit can jump) +unitDefJumpReloadFrames = {} -- frames to recharge ONE jump charge (jump_reload seconds * 30) +unitDefHasHeat = {} +unitDefHasSpeed = {} +unitDefHasReammo = {} +unitDefReammoFrames = {} -- nominal rearm time (frames) for the reammo gauge's smooth on-pad countdown +unitDefHasCaptureReload = {} +unitDefHasTeleport = {} +unitDefWeaponIcon = {} -- primary weapon's reload-badge icon (customParams.icon image path; nil = none drawn) +unitDefWeaponColor = {} -- {r,g,b} beam/projectile color of the classified weapon, for tinting its reload bar +unitDefIsComm = {} -- true for dynamic commanders (weapons assigned at runtime, not in the unitDef) + +-- Beams render far brighter than their raw rgbColor (additive glow/bloom), so normalize to full +-- value + a vibrance boost so e.g. (0.3,0,0.4) reads as bright saturated purple. Shared by the +-- per-unitDef scan below and by the widget for per-unit commander weapons. Returns {r,g,b} or nil. +local WEAPON_COLOR_VIBRANCE = 1.5 +function getNormalizedWeaponColor(visuals) + if not (visuals and visuals.colorR) then return nil end + local r, g, b = visuals.colorR, visuals.colorG, visuals.colorB + local m = math.max(r, g, b, 0.001) + return { + math.min(1, r / m * WEAPON_COLOR_VIBRANCE), + math.min(1, g / m * WEAPON_COLOR_VIBRANCE), + math.min(1, b / m * WEAPON_COLOR_VIBRANCE), + } +end + +-- Walk through unitdefs for the stuff we need: +for udefID, unitDef in pairs(UnitDefs) do + if unitDef.customParams and unitDef.customParams.nohealthbars then + unitDefIgnore[udefID] = true + end --ignore debug units + + -- SHIELDS + local shieldDefID = unitDef.shieldWeaponDef + local shieldPower = ((shieldDefID) and (WeaponDefs[shieldDefID].shieldPower)) or (-1) + if shieldPower > 1 then unitDefHasShield[udefID] = shieldPower end + + local isDynamic = false + + if unitDef.customParams and unitDef.customParams.dynamic_comm then + isDynamic = true + unitDefIsComm[udefID] = true + end + if not isDynamic then -- TODO if isDynamic then return end + local weapons = unitDef.weapons + local seenWeaponDef = {} -- dedup by weaponDef: synced dual/triple barrels share one timer + local weaponList = {} + + for i = 1, #weapons do + local wdid = weapons[i].weaponDef + local WeaponDef = WeaponDefs[wdid] + + if not WeaponDef then + + -- DGUN + elseif WeaponDef.manualFire then + unitDefDgun[udefID] = i + unitDefDgunReload[udefID] = WeaponDef.reload + + -- CAPTURE RELOAD + elseif WeaponDef.customParams and WeaponDef.customParams.post_capture_reload then + unitDefHasCaptureReload[udefID] = tonumber(WeaponDef.customParams.post_capture_reload) + + -- Skip "hidden" weapons (jump-landing crater, walk/takeoff effects, etc.): they're + -- effects of other abilities, not player-tracked weapons, and shouldn't eat an + -- ability slot (e.g. the Detriment's LANDING was starving its jump gauges). + elseif WeaponDef.customParams and WeaponDef.customParams.hidden then + + -- RELOAD: collect distinct reloadable weapons (one entry per weaponDef). + elseif WeaponDef.reload and WeaponDef.reload >= minReloadTime and not seenWeaponDef[wdid] then + seenWeaponDef[wdid] = true + weaponList[#weaponList + 1] = { + index = i, + reload = WeaponDef.reload, + color = getNormalizedWeaponColor(WeaponDef.visuals), + icon = WeaponDef.customParams and WeaponDef.customParams.icon, -- reload-badge icon (image path); nil = none drawn + } + end + + end + + -- Slowest weapon first; the slowest stays the "primary" (ch9), matching old behavior. + table.sort(weaponList, function(a, b) return a.reload > b.reload end) + if weaponList[1] then + unitDefWeapons[udefID] = weaponList + unitDefPrimaryWeapon[udefID] = weaponList[1].index + unitDefPrimaryReload[udefID] = weaponList[1].reload + unitDefWeaponColor[udefID] = weaponList[1].color -- tint the reload badge with the primary's beam color + unitDefWeaponIcon[udefID] = weaponList[1].icon -- primary weapon's reload-badge icon (or nil) + end + + -- SPECIAL ABILITY + if unitDef.customParams and unitDef.customParams.specialreloadtime then + unitDefHasAbility[udefID] = unitDef.customParams.specialreloadtime + unitDefAbilityIsMovement[udefID] = true -- all current abilities are movement-type (e.g., Swift Sprint) + end + + -- SCRIPT RELOAD + if unitDef.customParams and unitDef.customParams.script_reload then + unitDefScriptReload[udefID] = tonumber(unitDef.customParams.script_reload) * gameSpeed + end + + -- BURST COUNT (for script-based burst weapons like Picket, Hacksaw) + if unitDef.customParams and unitDef.customParams.script_burst then + unitDefBurstCount[udefID] = tonumber(unitDef.customParams.script_burst) + end + + -- GOO. Nominal (full-metal) replication time in frames = (cost/drain)*UPDATE_FREQUENCY(30), + -- so the overlay can count the gauge down smoothly "as if it had metal" instead of inferring + -- a jumpy rate. (grey_goo_defs.lua: drain is applied every 30 frames.) + if unitDef.customParams and unitDef.customParams.grey_goo then + unitDefHasGoo[udefID] = 1 + local cost = tonumber(unitDef.customParams.grey_goo_cost) + local drain = tonumber(unitDef.customParams.grey_goo_drain) + unitDefGooFrames[udefID] = (cost and drain and drain > 0) and (cost / drain * 30) or 1 + end + + -- HEAT + if unitDef.customParams and unitDef.customParams.heat_initial then + unitDefHasHeat[udefID] = 1 + end + + -- SPEED + if unitDef.customParams and unitDef.customParams.speed_bar then + unitDefHasSpeed[udefID] = 1 + end + + -- REAMMO (rearm time in frames, for the overlay's smooth countdown on the pad) + if unitDef.customParams and unitDef.customParams.reammoseconds then + unitDefHasReammo[udefID] = 1 + unitDefReammoFrames[udefID] = (tonumber(unitDef.customParams.reammoseconds) or 1) * 30 + end + + -- STOCKPILE + if unitDef.canStockpile then + unitDefCanStockpile[udefID] = unitDef.canStockpile + end + + -- TELEPORT + if unitDef.customParams and (unitDef.customParams.teleporter_throughput or unitDef.customParams.teleporter_is_beacon) then + unitDefHasTeleport[udefID] = 1 + end + end + + -- JUMP (jump_charges separate badges; each recharges in jump_reload seconds) + if unitDef.customParams and unitDef.customParams.canjump then + unitDefHasJump[udefID] = tonumber(unitDef.customParams.jump_charges) or 1 + unitDefJumpReloadFrames[udefID] = (tonumber(unitDef.customParams.jump_reload) or 0) * 30 + end +end + +-- MULTI-WEAPON: a unit whose only ability-pool usage is weapons (no dgun / burst / stockpile / +-- teleport / heat / speed / reammo / captureReload / scriptReload) shows every distinct weapon's +-- cooldown. The slowest stays the primary (ch9); the next ones (slowest first) fill the otherwise +-- unused ch10/11/12. Units with a special ability keep just their primary weapon (the special owns +-- the other channels), matching old behavior -- multi-weapon and specials don't co-occur in practice. +local extraWeaponChannels = { unitPrimaryCountChannel, unitSecondaryReloadChannel, unitSecondaryCountChannel } -- ch10,11,12 +for udefID, weaponList in pairs(unitDefWeapons) do + local hasOtherAbility = unitDefBurstCount[udefID] or unitDefDgun[udefID] or unitDefCanStockpile[udefID] + or unitDefHasTeleport[udefID] or unitDefHasHeat[udefID] or unitDefHasSpeed[udefID] + or unitDefHasReammo[udefID] or unitDefHasCaptureReload[udefID] or unitDefScriptReload[udefID] + if (not hasOtherAbility) and #weaponList > 1 then + local extras = {} + for n = 2, math.min(#weaponList, #extraWeaponChannels + 1) do + extras[#extras + 1] = { + channel = extraWeaponChannels[n - 1], + index = weaponList[n].index, + reload = weaponList[n].reload, + color = weaponList[n].color, + icon = weaponList[n].icon, + } + end + unitDefExtraWeapons[udefID] = extras + end +end + +-- ABILITY-SLOT ASSIGNMENT (the new generic-slot model; ADDITIVE — nothing reads this yet, so the fixed- +-- channel rendering below is untouched until the updater + overlay switch over). Ordered list per +-- unitDef that BOTH sides will walk identically: updater encodes each slot's value, overlay creates each +-- slot's bar (bartype/range/color from `kind`). Priority order below; past ABILITY_SLOTS_N it's dropped. +-- Reads full per-unitDef flags (runs before the budget guard clears overflow for the old 4-ch model). +-- NOTE: priority order is a tunable decision; morph is runtime (assigned when a unit starts morphing, +-- not per-unitDef) and is handled separately at that point. +local ABILITY_SLOTS_N = 5 +unitDefAbilitySlots = {} +for udefID, unitDef in pairs(UnitDefs) do + if not unitDefIgnore[udefID] then + local slots = {} + local function add(entry) + if #slots < ABILITY_SLOTS_N then slots[#slots + 1] = entry end + end + + -- WEAPONS (comm = runtime-assigned weapon; burst = one slot per projectile; else distinct + -- weapons slowest-first). scriptReload-only units (no static weapon) also get a slot. + if unitDefIsComm[udefID] then + -- Dynamic comms can equip two weapons (comm_weapon_id_1/_2); give each a slot. The + -- second only renders a bar at runtime if that weapon is actually equipped. + add({ kind = "commReload", commWeapon = 1 }) + add({ kind = "commReload", commWeapon = 2 }) + elseif unitDefBurstCount[udefID] then + for i = 1, unitDefBurstCount[udefID] do add({ kind = "burst", index = i }) end + elseif unitDefWeapons[udefID] then + for _, w in ipairs(unitDefWeapons[udefID]) do + add({ kind = "reload", weapon = w.index, reload = w.reload, class = w.class, color = w.color }) + end + elseif unitDefScriptReload[udefID] then + add({ kind = "scriptReload" }) + end + -- DGUN (non-movement) + if unitDefDgun[udefID] and not unitDefDgunIsMovement[udefID] then + add({ kind = "dgun", reload = unitDefDgunReload[udefID] }) + end + -- SHIELD (gauge) -- defensive, kept above the minor gauges + if unitDefHasShield[udefID] then add({ kind = "shield" }) end + -- STOCKPILE (progress + count = 2 slots) + if unitDefCanStockpile[udefID] then + add({ kind = "stockProg" }) + add({ kind = "stockCnt" }) + end + -- MOVEMENT (jump / movement-type dgun / movement-type special) + if unitDefHasJump[udefID] then + add({ kind = "jump" }) + elseif unitDefDgun[udefID] and unitDefDgunIsMovement[udefID] then + add({ kind = "moveDgun", reload = unitDefDgunReload[udefID] }) + elseif unitDefAbilityIsMovement[udefID] then + add({ kind = "moveAbility" }) + end + -- MINOR GAUGES / cooldowns + if unitDefHasTeleport[udefID] then add({ kind = "teleport" }) end + if unitDefHasHeat[udefID] then add({ kind = "heat" }) end + if unitDefHasSpeed[udefID] then add({ kind = "speed" }) end + if unitDefHasReammo[udefID] then add({ kind = "reammo" }) end + if unitDefHasCaptureReload[udefID] then add({ kind = "captureReload", reload = unitDefHasCaptureReload[udefID] }) end + if unitDefHasGoo[udefID] then add({ kind = "goo" }) end + + unitDefAbilitySlots[udefID] = slots + end +end + +-- Ability KIND registry: render mode + value encoding for the generic slots. Both sides read this. +-- render: how the shader draws it -- "duration" (modular countdown bar+badge), "gauge" (0-1 level +-- badge), "count" (integer), "rateETA" (build-style ETA badge, morph). +-- enc: how the updater encodes the 12-bit slot value -- "modular" (target frame mod 4096), +-- "percent" (0-100), "int" (count), "rateETA" (build-style bands). +-- Presentation (color/icon/exact bartype bits, layout zone) is taken from the existing barTypeMap at +-- bar-creation; this table is only the new render/enc info. +abilityKinds = { + reload = { render = "duration", enc = "modular" }, + commReload = { render = "duration", enc = "modular" }, + scriptReload = { render = "duration", enc = "modular" }, + burst = { render = "gauge", enc = "percent" }, -- per-projectile load fraction (0-1) + morphProg = { render = "duration", enc = "modular" }, + dgun = { render = "duration", enc = "modular" }, + moveDgun = { render = "duration", enc = "modular" }, + jump = { render = "duration", enc = "modular" }, + moveAbility = { render = "duration", enc = "modular" }, + reammo = { render = "duration", enc = "modular" }, + captureReload = { render = "duration", enc = "modular" }, + stockProg = { render = "duration", enc = "modular" }, + shield = { render = "gauge", enc = "percent" }, + heat = { render = "gauge", enc = "percent" }, + speed = { render = "gauge", enc = "percent" }, + teleport = { render = "gauge", enc = "percent" }, + stockCnt = { render = "count", enc = "int" }, + goo = { render = "gauge", enc = "percent" }, + morph = { render = "rateETA", enc = "rateETA" }, -- runtime-assigned +} + +-- ABILITY SLOT BUDGET GUARD +-- The 4 dynamic ability channels (ch9-12) hold: weapons (primary + multi-weapon extras, or burst at +-- one slot per projectile), dgun, stockpile (progress + count = 2), teleport, heat, speed, reammo, +-- captureReload. Walk them in priority order (weapons first); anything past the 4th is ignored -- its +-- tracking flag is cleared so it never silently overflows -- and named in a warning. Shield (ch5), +-- movement/jump (ch14) and the transient statuses (paralyze/disarm/slow/build/capture) have their own +-- fixed channels and are NOT part of this pool. +local ABILITY_SLOTS = 4 +for udefID, unitDef in pairs(UnitDefs) do + if not unitDefIgnore[udefID] then + -- weapon channels actually used: burst (N), else primary + assigned multi-weapon extras. + local weaponSlots = unitDefBurstCount[udefID] + or (unitDefExtraWeapons[udefID] and (#unitDefExtraWeapons[udefID] + 1)) + or (unitDefPrimaryWeapon[udefID] and 1) or 0 + -- {slot cost, label, clear-fn} in priority order; weapons have no clear-fn (never dropped) + local pool = { + {weaponSlots, "weapon"}, + {(unitDefDgun[udefID] and not unitDefDgunIsMovement[udefID]) and 1 or 0, "dgun", + function() unitDefDgun[udefID] = nil; unitDefDgunReload[udefID] = nil end}, + {unitDefCanStockpile[udefID] and 2 or 0, "stockpile", + function() unitDefCanStockpile[udefID] = nil end}, + {unitDefHasTeleport[udefID] and 1 or 0, "teleport", + function() unitDefHasTeleport[udefID] = nil end}, + {unitDefHasHeat[udefID] and 1 or 0, "heat", + function() unitDefHasHeat[udefID] = nil end}, + {unitDefHasSpeed[udefID] and 1 or 0, "speed", + function() unitDefHasSpeed[udefID] = nil end}, + {unitDefHasReammo[udefID] and 1 or 0, "reammo", + function() unitDefHasReammo[udefID] = nil end}, + {unitDefHasCaptureReload[udefID] and 1 or 0, "captureReload", + function() unitDefHasCaptureReload[udefID] = nil end}, + } + local used, dropped = 0, nil + for i = 1, #pool do + local cost, label, clear = pool[i][1], pool[i][2], pool[i][3] + if cost > 0 then + if used + cost <= ABILITY_SLOTS then + used = used + cost + elseif clear then + clear() + dropped = dropped and (dropped .. ", " .. label) or label + end + end + end + if dropped then + Spring.Echo("[Unit Overlay] ability-slot overflow on " .. tostring(unitDef.name or udefID) .. + " (>" .. ABILITY_SLOTS .. " slots): ignoring " .. dropped) + end + end +end diff --git a/LuaUI/Widgets/Shaders/UnitOverlayGL4.frag.glsl b/LuaUI/Widgets/Shaders/UnitOverlayGL4.frag.glsl new file mode 100644 index 0000000000..6ed523db0e --- /dev/null +++ b/LuaUI/Widgets/Shaders/UnitOverlayGL4.frag.glsl @@ -0,0 +1,229 @@ +#version 420 +#extension GL_ARB_uniform_buffer_object : require +#extension GL_ARB_shading_language_420pack: require + +// This file is going to be licensed under some sort of GPL-compatible license, but authors are dragging +// their feet. Avoid copying for now (unless this header rots for years on end), and check back later. +// See https://github.com/ZeroK-RTS/Zero-K/issues/5328 + +//__ENGINEUNIFORMBUFFERDEFS__ +//__DEFINES__ + +#line 30000 +in DataGS { + vec4 g_color; + vec4 g_uv; + vec4 g_rect; + vec2 g_loc; + float g_corner_radius; + float g_barmode; // 0 = normal textured quad (glyph/icon), 1 = horizontal bar body, 3 = radial timer badge + float g_fill; + float g_extracolor; + vec4 g_cluster; // icon cluster: xy = rank cell origin, zw = group cell origin (<0 = absent) + vec4 g_clusterCol; // rgb = rank tint + vec4 g_effect; // center-icon status effects: x = slow, y = disarm, z = paralyze, w = build (0 elsewhere) + float g_cloak; // center-icon cloak fraction 0..1 (own/allied only); fades the icon alpha +}; + +uniform sampler2D iconAtlasTex; +uniform float barBorderWidth; +uniform float trackDarken; // brightness of the empty/remaining portion of a bar relative to the filled part (1.0 = same, lower = darker) +out vec4 fragColor; + +// Signed distance from p to a rounded box of the given half-size and corner radius, centered +// on the origin. Negative = inside. Used for both the outer silhouette and the inset boundary +// so "which zone is this pixel in" is always a single continuous threshold, not several +// independently-folded/clamped tests that can disagree by a sub-pixel epsilon at the seam +// between zones (the previous quadrant-fold + nested-if approach did exactly that). +float roundedBoxSDF(vec2 p, vec2 halfSize, float radius) { + vec2 q = abs(p) - halfSize + radius; + return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - radius; +} + +// Signed distance to a regular polygon of n sides, apothem r, centered on origin, point-up. +float polygonSDF(vec2 p, float r, float n) { + float an = 3.14159265 / n; + vec2 acs = vec2(cos(an), sin(an)); + float bn = mod(atan(p.x, p.y) + an, 2.0 * an) - an; + p = length(p) * vec2(cos(bn), abs(sin(bn))); + p -= r * acs; + p.y += clamp(-p.y, 0.0, r * acs.y); + return length(p) * sign(p.x); +} + +void main(void) +{ + vec2 rectCenter = g_rect.xy + g_rect.zw * 0.5; + if (roundedBoxSDF(g_loc - rectCenter, g_rect.zw * 0.5, g_corner_radius) > 0.0) { + discard; + } + + if (g_barmode > 2.5) { + // Radial timer badge: regular polygon (g_uv.x = side count, <2.5 = circle) filled clockwise + // from the top (12 o'clock). g_fill = lit fraction; lit area is full color, the rest dim. + vec2 c = g_rect.xy + g_rect.zw * 0.5; + vec2 p = g_loc - c; + float quadHalf = min(g_rect.z, g_rect.w) * 0.5; + float apothem = quadHalf * 0.5; // shape sized by side-centers; corners reach the quad edge + float sides = g_uv.x; + float sd; + if (sides < 2.5) { + sd = length(p) - apothem; // circle + } else { + float circum = apothem / cos(3.14159265 / sides); + sd = polygonSDF(vec2(p.x, -p.y), circum, sides); // flip Y so polygons point up + } + // NOTE: no early "outside the polygon" discard -- the icon is allowed to spill past the badge + // silhouette (its square corners exceed the polygon), matching the old separate-quad look while + // staying one quad / one depth. The fill is drawn only inside the badge; the icon draws anywhere. + float ang = atan(p.x, p.y); // 0 at top, increasing clockwise + if (ang < 0.0) ang += 6.28318530718; + float frac = ang / 6.28318530718; + bool lit = frac <= g_fill; + bool insideBadge = (sd <= 0.0); + vec3 fillRGB = g_color.rgb * (lit ? 1.0 : 0.25); + // Icon (g_uv.w > 0.5): atlas cell origin g_uv.yz, sized to the badge's inscribed region but NOT + // clipped to the polygon -- corners outside the badge still draw. + vec4 ic = vec4(0.0); + if (g_uv.w > 0.5 && abs(p.x) <= apothem && abs(p.y) <= apothem) { + vec2 frac2 = (p / apothem) * 0.5 + 0.5; // [0,1] across the central icon region, y up + vec2 cell = vec2(1.0 / float(ICONATLAS_COLS), 1.0 / float(ICONATLAS_ROWS)); + ic = texture(iconAtlasTex, g_uv.yz + frac2 * cell); + } + vec3 rgb; + float alpha; + if (insideBadge) { + rgb = mix(fillRGB, ic.rgb, ic.a); // icon over the gauge fill + alpha = g_color.a; + } else { + rgb = ic.rgb; // icon spilling outside the badge + alpha = ic.a * g_color.a; + } + if (alpha < 0.05) discard; // nothing here (outside badge, no icon) + fragColor = vec4(rgb, alpha); + return; + } + + if (g_barmode > 0.5) { + // Bar body: the outer rect is the background band; barBorderWidth inset from it is the + // track/fill area. Decide which zone this pixel is in and shade it accordingly, + // replacing what used to be 3 separately-emitted quads (background/tint/fill). + vec4 inset = vec4(g_rect.x + barBorderWidth, g_rect.y + barBorderWidth, g_rect.z - 2.0 * barBorderWidth, g_rect.w - 2.0 * barBorderWidth); + + float outerYFrac = clamp((g_loc.y - g_rect.y) / g_rect.w, 0.0, 1.0); + + // The track/fill layer has its own (smaller) rounded corners nested inside the + // background's corners -- a single SDF threshold against the inset rect handles both + // the straight edges and the corners in one continuous test, so there's no seam between + // "inside" and "outside" for the renderer to disagree on by a fraction of a pixel. + vec2 insetCenter = inset.xy + inset.zw * 0.5; + bool insideInset = roundedBoxSDF(g_loc - insetCenter, inset.zw * 0.5, SMALLERCORNER) <= 0.0; + + if (!insideInset) { + fragColor = mix(BGBOTTOMCOLOR, BGTOPCOLOR, outerYFrac) + g_extracolor; + } else { + float insetXFrac = clamp((g_loc.x - inset.x) / inset.z, 0.0, 1.0); + float insetT = clamp((g_loc.y - inset.y) / inset.w, 0.0, 1.0); + + // Filled vs empty is just a brightness factor: the empty (un-progressed) portion is a + // darkened version of the same fill, opaque throughout -- not a see-through wash. + float bright = (insetXFrac <= g_fill) ? 1.0 : trackDarken; + // Horizontal bar: bottom-darken gradient, optionally modulated by the fill pattern. + vec3 surface = mix(g_color.rgb * BOTTOMDARKENFACTOR, g_color.rgb, insetT); + if (g_uv.z > 0.5) { + // Fill is a BARFILLCELLS-wide run in the runtime icon atlas starting at the cell + // whose atlas origin is g_uv.xy. Walk it horizontally across the bar (insetXFrac) + // and vertically within the one cell (insetT, bottom-up like the icon path). Inset + // by half an atlas texel so bilinear filtering never bleeds in the neighboring + // cell above/below or the adjacent bar at the run's ends. + vec2 atlasTexel = vec2(0.5 / (float(ICONATLAS_COLS) * 64.0), 0.5 / (float(ICONATLAS_ROWS) * 64.0)); + float spanW = float(BARFILLCELLS) / float(ICONATLAS_COLS); + float cellH = 1.0 / float(ICONATLAS_ROWS); + vec2 atlasUV = vec2( + g_uv.x + atlasTexel.x + insetXFrac * (spanW - 2.0 * atlasTexel.x), + g_uv.y + atlasTexel.y + insetT * (cellH - 2.0 * atlasTexel.y)); + surface *= texture(iconAtlasTex, atlasUV).rgb; + } + fragColor = vec4(surface * bright, g_color.a); + } + if (fragColor.a < 0.05) discard; + return; + } + + // Glyphs/icons (barmode 0) all sample the runtime icon atlas now. + vec4 col = g_color * texture(iconAtlasTex, vec2(g_uv.x, 1.0 - g_uv.y)); + // Icon cluster: the center unit icon composites a rank badge (top-left), group number (bottom-right) + // and current command (bottom-left) onto this SAME quad, so all share one depth (no z-fight, nothing + // sorts between them). g_cluster holds atlas-cell INDICES: x=rank, y=group, z=command (<0 = absent). + if (g_cluster.x >= 0.0 || g_cluster.y >= 0.0 || g_cluster.z >= 0.0) { + vec2 frac = (g_loc - g_rect.xy) / g_rect.zw; // [0,1] across the quad (y up) + vec2 cellsz = vec2(1.0 / float(ICONATLAS_COLS), 1.0 / float(ICONATLAS_ROWS)); + // Sample only the cell's interior: bilinear filtering at the exact cell edge bleeds the neighbor, + // so inset by half a texel (atlas is 64px/cell) and map each quadrant's [0,1] across that interior. + vec2 texel = vec2(0.5 / (float(ICONATLAS_COLS) * 64.0), 0.5 / (float(ICONATLAS_ROWS) * 64.0)); + vec2 inner = cellsz - 2.0 * texel; + if (g_cluster.x >= 0.0 && frac.x < 0.5 && frac.y > 0.5) { // rank -> top-left + vec2 o = vec2(mod(g_cluster.x, float(ICONATLAS_COLS)), floor(g_cluster.x / float(ICONATLAS_COLS))) * cellsz; + vec4 r = texture(iconAtlasTex, o + texel + vec2(frac.x, frac.y - 0.5) * 2.0 * inner); + r.rgb *= g_clusterCol.rgb; + col = mix(col, r, r.a); + } + if (g_cluster.y >= 0.0 && frac.x > 0.5 && frac.y < 0.5) { // group -> bottom-right + vec2 o = vec2(mod(g_cluster.y, float(ICONATLAS_COLS)), floor(g_cluster.y / float(ICONATLAS_COLS))) * cellsz; + vec4 grp = texture(iconAtlasTex, o + texel + vec2(frac.x - 0.5, frac.y) * 2.0 * inner); + grp.rgb *= vec3(0.4, 1.0, 0.4); // group number green tint + col = mix(col, grp, grp.a); + } + if (g_cluster.z >= 0.0 && frac.x < 0.5 && frac.y < 0.5) { // current command -> bottom-left + vec2 o = vec2(mod(g_cluster.z, float(ICONATLAS_COLS)), floor(g_cluster.z / float(ICONATLAS_COLS))) * cellsz; + vec4 c = texture(iconAtlasTex, o + texel + vec2(frac.x, frac.y) * 2.0 * inner); + col = mix(col, c, c.a); + } + } + + // Status-effect tint on the center unit icon, echoing the on-model effects. The bars/badges already + // show the precise states; this is only a secondary at-a-glance cue for radar range (icon-only), so + // keep the icon legible: (a) show only the DOMINANT effect rather than stacking washes into mud, and + // (b) tint hue-only -- the effect colour is scaled by the icon's own luminance, so the silhouette and + // relative brightness (hence team shade) read through. g_effect is non-zero only for the center icon. + // Colours mirror gfx_paralyze_effect; priority paralyze > disarm > slow (most disabling first). + vec3 effectColor = vec3(0.0); + float effectAmt = 0.0; + if (g_effect.z > 0.001) { // paralyze (EMP): light blue + effectColor = vec3(0.49, 0.5, 1.0); + effectAmt = clamp(g_effect.z * 0.55, 0.0, 0.55); + } else if (g_effect.y > 0.001) { // disarm: desaturated khaki/tan + effectColor = vec3(0.7, 0.7, 0.55); + effectAmt = clamp(g_effect.y * 0.5, 0.0, 0.5); + } else if (g_effect.x > 0.001) { // slow: magenta + effectColor = vec3(1.0, 0.1, 1.0); + effectAmt = clamp(sqrt(g_effect.x) * 0.5, 0.0, 0.5); + } + if (effectAmt > 0.0) { + float lum = dot(col.rgb, vec3(0.299, 0.587, 0.114)); + vec3 tinted = effectColor * (0.35 + lum); // effect hue, icon's own brightness -> silhouette survives + col.rgb = mix(col.rgb, tinted, effectAmt); + } + + // CONSTRUCTION: while a unit builds, its nanoframe rises up the model; echo that on the icon as a fill + // rising bottom-to-top by build progress. g_effect.w is the progress fraction in (0,1) while under + // construction, 0 otherwise. Legibility-first: the not-yet-built (upper) part is only dimmed -- never + // erased -- and a bright team-coloured line marks the build front. Static (no time term) since the + // fragment stage can't be relied on to carry timeInfo. + float buildP = g_effect.w; + if (buildP > 0.001) { + vec2 fq = (g_loc - g_rect.xy) / g_rect.zw; // [0,1] across the icon quad, y up + if (fq.y > buildP) col.rgb *= 0.4; // not yet built: dimmed, still visible + float line = 1.0 - clamp(abs(fq.y - buildP) * 12.0, 0.0, 1.0); // bright front at the build height + col.rgb = mix(col.rgb, g_color.rgb * 1.4, line); // team-coloured build line + } + + // CLOAK (own/allied only -- enemies are gated out in the VS): fade the icon like the model fades when + // cloaked. Floored at 0.45 alpha so your own cloaked units stay locatable rather than vanishing. + if (g_cloak > 0.001) { + col.a *= mix(1.0, 0.45, clamp(g_cloak, 0.0, 1.0)); + } + + fragColor = col; + if (fragColor.a < 0.05) discard; +} diff --git a/LuaUI/Widgets/Shaders/UnitOverlayGL4.geom.glsl b/LuaUI/Widgets/Shaders/UnitOverlayGL4.geom.glsl new file mode 100644 index 0000000000..f4e410501d --- /dev/null +++ b/LuaUI/Widgets/Shaders/UnitOverlayGL4.geom.glsl @@ -0,0 +1,684 @@ +#version 330 +#extension GL_ARB_uniform_buffer_object : require +#extension GL_ARB_shading_language_420pack: require + +// This file is going to be licensed under some sort of GPL-compatible license, but authors are dragging +// their feet. Avoid copying for now (unless this header rots for years on end), and check back later. +// See https://github.com/ZeroK-RTS/Zero-K/issues/5328 + +//__ENGINEUNIFORMBUFFERDEFS__ +//__DEFINES__ +layout(points) in; +layout(triangle_strip, max_vertices = MAXVERTICES) out; +#line 20000 + +uniform float iconDistance; +uniform float skipGlyphsNumbers; // <0.5 means none, <1.5 means percent only, >1.5 means nothing, just bars +uniform float vbarUserX; // weapon-bar horizontal offset from the unit +uniform float vbarSize; // weapon-bar size multiplier +uniform float iconSize; // unit icon half-size in BARWIDTH units +uniform float barBorderWidth; // thickness of the decorative band around the bar's track/fill +uniform float reloadThreshold; // seconds: weapons faster than this hide the timer (commanders show "ready") +uniform float digitAtlasStart; // atlas cell index of the digit strip's first glyph ('s'); +1='%', then 9..0 +uniform float jumpIconCell; // atlas cell of the jump command icon, composited into jump-charge gauges +uniform float pulseAlpha; // oscillating alpha for BITPULSE hovering icons (flashing build/chicken icons) +uniform float rowOffset; // the row above the bars (states + status): extra vertical gap above the top bar +uniform float rowSize; // shared size for the hovering-icon states, status badges and weapon/reload badges +uniform float rowSpacing; // badge/row spacing multiplier (now applied to ALL badge zones: row, weapon + // columns, below-zone, so spacing is uniform everywhere) +uniform float isFeature; // 1 for the feature draw pass (their status channels differ; keep fixed layout) +uniform float overallScale; // overall overlay scale, multiplied alongside the per-unit size into the + // shared transform (so it scales every component uniformly) +// Unified spacing constants (in "half-size" units of each element): +#define BADGE_PITCH 2.6 // center-to-center spacing between adjacent badges/icons (all zones) +#define BAR_PITCH 1.2 // center-to-center spacing between stacked horizontal bars (and the row's + // per-bar clearance, so the row always clears the stack by one bar exactly) +#define ROW_GAP 0.5 // extra gap (in badge half-sizes) between the top bar and the row's bottom edge, + // on top of the bar clearance, so the states/status row isn't glued to the bar +uniform float barSize; // size multiplier for the horizontal bar bodies + their numbers (bar size option) +uniform float barOffset; // extra world-space gap between the unit icon and the bars (bar offset option) +uniform float barSpacing; // gap multiplier between stacked horizontal bars (bar spacing option) +uniform float belowBadgeHeight; // below-zone ability badges (jump/morph/teleport): raise(+)/lower(-) them +uniform float overlayDepthBand; // >0 squeezes the overlay into a near-plane sliver so the world never + // occludes it (the engine ignores depth-buffer clears in DrawWorld); + // overlays still depth-write so they sort among themselves. 0 = normal depth. +uniform float statusFadeDistance; // camera distance beyond which status/state row icons are hidden (0 = never) +uniform float iconHideDistance; // camera distance below which the center unit icon is hidden (0 = never) +#ifdef SCREENSPACE +uniform float screenWidth; +uniform float screenHeight; +#endif + +in DataVS { // I recall the sane limit for cache coherence is like 48 floats per vertex? try to stay under that! + uint v_numvertices; + vec4 v_mincolor; + vec4 v_maxcolor; + vec4 v_centerpos; + vec4 v_uvoffsets; + vec4 v_parameters; + float v_sizeModifier; + float v_range; + float v_aboveBars; // count of visible "above" bars (for placing the row above the top bar) + float v_rowSlot; // combined centered slot in the row above the bars (states + status badges) + float v_iconCloak; // center-icon cloak fraction 0..1 (own/allied only); fades the icon alpha + uvec4 v_bartype_index_ssboloc; +} dataIn[]; + +out DataGS { + vec4 g_color; // pure rgba (for bar bodies: the health/fill color, used to derive fill+track shades in FS) + vec4 g_uv; // xy is trivially uv coords, z is fill-uses-texture flag (bar bodies only), w is icon atlas flag + vec4 g_rect; + vec2 g_loc; + float g_corner_radius; + float g_barmode; // 0 = normal textured quad (glyph/icon), 1 = horizontal bar body, 2 = vertical bar body + float g_fill; // fill fraction 0..1, only meaningful when g_barmode != 0 + float g_extracolor; // additive blink amount for the background band, only meaningful when g_barmode != 0 + // Icon-cluster composite (barmode 0, the center unit icon only): rank badge + group number drawn + // onto the SAME quad as the icon so they share one depth. xy = rank atlas-cell origin, zw = group + // cell origin; an origin < 0 means "absent". g_clusterCol.rgb = rank tint. + vec4 g_cluster; + vec4 g_clusterCol; + // Status-effect magnitudes for the center unit icon (barmode 0): x = slow, y = disarm, + // z = paralyze, w = build progress. Zero for every other quad (only the icon VS fills it). + vec4 g_effect; + float g_cloak; // center-icon cloak fraction 0..1 (own/allied only); fades the icon alpha +}; + +mat3 rotY; +float localLift = 0.0; // up-axis offset added INSIDE the shared transform (so it scales with the overlay + // like everything else); used for the icon->bar gap (barOffset) +vec4 centerpos; +vec4 uvoffsets; +float zoffset; +float yoffset; // camera-forward (billboard Z) offset +float xoffset; +float depthbuffermod; +mat4 overlayXform; // shared world transform: unit anchor (centerpos) · billboard · (BARSCALE·overallScale·unitSize) +float posScale = 1.0; // per-component sub-scale, applied to local coords only (g_loc/g_rect stay raw for the FS) +// Icon-cluster composite (set only in the center-icon branch; default = absent for everything else): +// atlas-cell INDICES (the FS turns each into a cell origin), <0 = absent. x=rank (top-left), +// y=group (bottom-right), z=command (bottom-left). +vec3 clusterCells = vec3(-1.0); +vec3 clusterRankColor = vec3(1.0); // rank badge tint + +// Shared layout transform. Every component builds its quad in local billboard space (x=right, z=up, +// y=cam-forward) around the unit anchor; this applies the ONE shared transform plus the per-component +// posScale, so all components share the same anchor/billboard/scale and differ only by local offset. +vec4 overlayVertexClip(vec2 pos) { +#ifdef SCREENSPACE + // Screen-space bars are a separate (currently inactive) widget; keep this path's scaling as-is plus + // the per-component posScale + localLift. overallScale lives only in the world transform. + vec2 p = (vec2(pos.x + xoffset, pos.y - zoffset) * posScale + vec2(0.0, localLift)) * BARSCALE; + vec2 screenPos = centerpos.xy + vec2(p.x, -p.y); + return vec4(screenPos.x / screenWidth * 2.0 - 1.0, 1.0 - screenPos.y / screenHeight * 2.0, 0.0, 1.0); +#else + // localLift is added AFTER posScale but still inside overlayXform, so it scales with the overlay (s) + // but not with the per-component posScale -- a clean, proportional gap routed through the transform. + vec3 local = vec3(pos.x + xoffset, yoffset, pos.y - zoffset) * posScale + vec3(0.0, 0.0, localLift); + return cameraViewProj * (overlayXform * vec4(local, 1.0)); +#endif +} + +#define HALFPIXEL 0.0019765625 + +#define BARTYPE dataIn[0].v_bartype_index_ssboloc.x +#define BARALPHA dataIn[0].v_parameters.y +#define GLYPHALPHA dataIn[0].v_parameters.z +#define UVOFFSET dataIn[0].v_parameters.w +#define UNIFORMLOC dataIn[0].v_bartype_index_ssboloc.z + +#define BITSHOWGLYPH 2u +#define BITTIMELEFT 8u +#define BITINVERSE 32u +#define BITALWAYSSHOW 8192u +#define BITINTEGERNUMBER 16u +#define BITVERTICAL 256u +#define BITLEFT 512u +#define BITRIGHT 1024u +#define BITICON 4096u +#define BITICONROW 16384u +#define BITPULSE 32768u +#define BITCONSTRUCTION 65536u +#define BITGAUGE 131072u +#define BITICONCORNER 262144u +#define BITJUMPCHARGE 1048576u +#define BITRATEETA 2097152u + +float iconAtlasFlag = 0.0; + +// Goo/Morph (8) and Movement (14) stack below the unit; everything else (incl. build) stacks above. +bool isChannelBelow(uint channel) { + return channel == 8u || channel == 14u; +} + +// Final depth for a billboard vertex. Adds the per-element layering offset (depthbuffermod), and, when +// overlayDepthBand > 0, remaps the whole overlay into a thin band just off the near plane so it always +// wins the depth test against the world while still ordering among itself (depthbuffermod kept in NDC). +void applyOverlayDepth() { +#ifdef SCREENSPACE + gl_Position.z += depthbuffermod; +#else + if (overlayDepthBand > 0.0) { + // The engine uses a [0,1] depth range with the near plane at 0 and GL_LESS, so the smallest + // depth wins (matches the layering: more-negative depthbuffermod = more in front, e.g. the + // integer count at -0.004 sits in front of the gauge). Squeeze the whole overlay into a thin + // band just past the near plane so it always beats world geometry (in a top-down view units, + // terrain and trees all sit far from the near plane), while keeping the natural depth and the + // depthbuffermod layering inside the band so the overlays still sort among themselves. + // band * ndc dominates so overlays sort by true camera distance (nearer unit on top); + // depthbuffermod gets a small sub-slice that only breaks ties (the layering inside one + // unit's overlay, where ndc is shared). Scaling it down keeps distance ordering between + // near-equidistant units from being overridden by another unit's internal layering, while + // 0.0005-sized steps near the near plane are still far above any z-fighting threshold. + float ndc = clamp(gl_Position.z / gl_Position.w, 0.0, 1.0); // natural [0,1] depth + float z = 0.005 + overlayDepthBand * ndc + depthbuffermod * 0.1; // near-plane sliver + layering + gl_Position.z = z * gl_Position.w; + } else { + gl_Position.z += depthbuffermod; + } +#endif +} + +void emitRectangleVertex(vec2 pos, vec4 corners, float corner_radius, float useTexture, vec2 uv, vec4 color) { + g_uv.xy = vec2(uv.x, 1.0 - uv.y); + g_uv.w = iconAtlasFlag; + gl_Position = overlayVertexClip(pos); + applyOverlayDepth(); + + g_uv.z = useTexture; // this tells us to use texture + g_color = color; + g_color.a *= dataIn[0].v_parameters.z; // blend with text/icon fade alpha + g_rect = corners; + g_loc = pos; + g_corner_radius = corner_radius; + g_barmode = 0.0; + g_fill = 0.0; + g_extracolor = 0.0; + g_cluster = vec4(clusterCells, 0.0); + g_clusterCol = vec4(clusterRankColor, 0.0); + g_effect = dataIn[0].v_uvoffsets; // status-effect magnitudes (icon only; 0 elsewhere) + g_cloak = dataIn[0].v_iconCloak; // cloak fade (icon only; 0 elsewhere) + + EmitVertex(); +} + +// Single-quad bar body: the FS decides per-pixel whether it's drawing the background band, +// the empty track, or the filled portion, instead of us emitting 3 separate quads here. +// The fill texture (only used by horizontal bars) is stretched across the inset area same as +// the old separate fill quad was, so we only pass its origin in the atlas -- the FS reconstructs +// the actual sample point from the pixel's fraction across the inset, not from interpolated UVs. +void emitBarVertex(vec2 pos, vec4 rect, float corner_radius, float barmode, float fill, float extracolor, float fillUsesTexture, vec2 fillUVOrigin, vec4 healthcolor) { + gl_Position = overlayVertexClip(pos); + applyOverlayDepth(); + + g_color = healthcolor; + g_color.a *= dataIn[0].v_parameters.z; // blend with text/icon fade alpha + // w carries the vertical-bar blend mode for the FS: 1 = screen (white core stays white, glows), + // 0 = multiply (tracer/other bars: colored projectiles on dark "air"). Energy (col 0) and + // lightning (col 12) are glowing beam/arc types -> screen; everything else multiplies. + // (Unused by horizontal bars.) + float screenBlend = (UVOFFSET < 0.5 || abs(UVOFFSET - 12.0) < 0.5) ? 1.0 : 0.0; + g_uv = vec4(fillUVOrigin.x, fillUVOrigin.y, fillUsesTexture, screenBlend); + g_rect = rect; + g_loc = pos; + g_corner_radius = corner_radius; + g_barmode = barmode; + g_fill = fill; + g_extracolor = extracolor; + g_cluster = vec4(-1.0); // bars never composite an icon cluster + g_clusterCol = vec4(1.0); + g_effect = vec4(0.0); // bars are not the center icon + g_cloak = 0.0; + + EmitVertex(); +} + +void emitBarRectangle(vec4 destination, float corner_radius, float barmode, float fill, float extracolor, float fillUsesTexture, vec2 fillUVOrigin, vec4 healthcolor) { + float dl = destination.x; + float db = destination.y; + float dr = destination.x + destination.z; + float dt = destination.y + destination.w; + + emitBarVertex(vec2(dl, db), destination, corner_radius, barmode, fill, extracolor, fillUsesTexture, fillUVOrigin, healthcolor); + emitBarVertex(vec2(dl, dt), destination, corner_radius, barmode, fill, extracolor, fillUsesTexture, fillUVOrigin, healthcolor); + emitBarVertex(vec2(dr, db), destination, corner_radius, barmode, fill, extracolor, fillUsesTexture, fillUVOrigin, healthcolor); + emitBarVertex(vec2(dr, dt), destination, corner_radius, barmode, fill, extracolor, fillUsesTexture, fillUVOrigin, healthcolor); + + EndPrimitive(); +} + +// Radial timer badge: a billboard quad whose FS draws a regular polygon (sides = magnitude) with a +// clockwise-from-top angular fill. g_uv.x carries the side count, g_fill carries the lit fraction. +// iconOrigin = atlas cell origin (uv) of the icon to composite inside the badge; hasIcon>0.5 enables it. +// The FS draws the polygon fill AND the icon on this one quad, so they share a depth (no sort gap). +void emitRadialVertex(vec2 pos, vec4 rect, float sides, float litFrac, vec4 color, vec2 iconOrigin, float hasIcon) { + gl_Position = overlayVertexClip(pos); + applyOverlayDepth(); + + g_color = color; + g_color.a *= dataIn[0].v_parameters.z; + g_uv = vec4(sides, iconOrigin.x, iconOrigin.y, hasIcon); + g_rect = rect; + g_loc = pos; + g_corner_radius = 0.0; + g_barmode = 3.0; // radial badge + g_fill = litFrac; + g_extracolor = 0.0; + g_cluster = vec4(-1.0); // radial badges never composite an icon cluster + g_clusterCol = vec4(1.0); + g_effect = vec4(0.0); // radial badges are not the center icon + g_cloak = 0.0; + EmitVertex(); +} + +void emitRadialBadge(vec4 d, float sides, float litFrac, vec4 color, vec2 iconOrigin, float hasIcon) { + emitRadialVertex(vec2(d.x, d.y), d, sides, litFrac, color, iconOrigin, hasIcon); + emitRadialVertex(vec2(d.x, d.y + d.w), d, sides, litFrac, color, iconOrigin, hasIcon); + emitRadialVertex(vec2(d.x + d.z, d.y), d, sides, litFrac, color, iconOrigin, hasIcon); + emitRadialVertex(vec2(d.x + d.z, d.y + d.w), d, sides, litFrac, color, iconOrigin, hasIcon); + EndPrimitive(); +} + +void emitRectangle(vec4 destination, vec4 corners, float corner_radius, float useTexture, vec4 texture, vec4 topColor, vec4 bottomColor) { + // bottom = .x + // left = .y + // height = .z + // width = .w + + float dl = destination.x; + float db = destination.y; + float dr = destination.x + destination.z; + float dt = destination.y + destination.w; + + // Sample only the cell interior: inset the atlas cell rect by half a texel (64px/cell) so bilinear + // filtering can't bleed in the neighboring cell at the icon/glyph edges. + vec2 atlasTexel = vec2(0.5 / (float(ICONATLAS_COLS) * 64.0), 0.5 / (float(ICONATLAS_ROWS) * 64.0)); + texture.xy += atlasTexel; + texture.zw -= 2.0 * atlasTexel; + + float tl = texture.x; + float tb = texture.y; + float tr = texture.x + texture.z; + float tt = texture.y + texture.w; + + emitRectangleVertex(vec2(dl, db), corners, corner_radius, useTexture, vec2(tl, tb), bottomColor); + emitRectangleVertex(vec2(dl, dt), corners, corner_radius, useTexture, vec2(tl, tt), topColor); + emitRectangleVertex(vec2(dr, db), corners, corner_radius, useTexture, vec2(tr, tb), bottomColor); + emitRectangleVertex(vec2(dr, dt), corners, corner_radius, useTexture, vec2(tr, tt), topColor); + + EndPrimitive(); +} + +// Emit one digit/symbol glyph from the digit strip in the runtime icon atlas. leftX is the left +// edge in billboard X; cellIndex is the absolute atlas cell. Sets iconAtlasFlag so the FS samples +// iconAtlasTex (caller restores it to 0 afterwards). +void emitGlyphCell(float leftX, float cellIndex) { + iconAtlasFlag = 1.0; + float gcol = mod(cellIndex, float(ICONATLAS_COLS)); + float grow = floor(cellIndex / float(ICONATLAS_COLS)); + emitRectangle( + vec4(leftX, 0, BARHEIGHT, BARHEIGHT), + vec4(leftX, 0, BARHEIGHT, BARHEIGHT), + 0.0, + 1.0, + vec4(gcol / float(ICONATLAS_COLS), grow / float(ICONATLAS_ROWS), + 1.0 / float(ICONATLAS_COLS), 1.0 / float(ICONATLAS_ROWS)), + vec4(1, 1, 1, 1), + vec4(1, 1, 1, 1) + ); +} + +#line 22000 +void main(){ + centerpos = dataIn[0].v_centerpos; + yoffset = 0.0; + +#ifndef SCREENSPACE + rotY = mat3(cameraViewInv[0].xyz,cameraViewInv[2].xyz, cameraViewInv[1].xyz); // swizzle cause we use xz, + // ONE shared transform for every component: translate to the unit anchor (centerpos -- already the + // unit position + half height from the VS), rotate into the camera-facing billboard frame, and apply + // the shared scale (BARSCALE · overallScale · per-unit size). Each component then only adds its own + // local offset (+ a per-component posScale), so anchor/billboard/scale are identical across all of them. + float s = BARSCALE * overallScale * dataIn[0].v_sizeModifier; + overlayXform = mat4(vec4(rotY[0] * s, 0.0), + vec4(rotY[1] * s, 0.0), + vec4(rotY[2] * s, 0.0), + vec4(centerpos.xyz, 1.0)); +#endif + vec4 g_rect; + float g_corner_radius; + + g_color = vec4(1.0, 0.0, 1.0, 1.0); // a very noticeable default color + + uvoffsets = dataIn[0].v_uvoffsets; // if an atlas is used, then use this, otherwise dont + + float health = min(1, dataIn[0].v_parameters.x); + if (BARALPHA < MINALPHA) return; // Dont draw below 50% transparency + + // All the early bail conditions to not draw full/empty bars + if (dataIn[0].v_numvertices == 0u) return; // for hiding the build bar when full health + + depthbuffermod = 0.001; + float extraColor = 0.0; // (status bars no longer flash) + + float camDist = length(cameraViewInv[3].xyz - centerpos.xyz); + + if ((BARTYPE & BITICON) != 0u) { + iconAtlasFlag = 1.0; + float iconHalf; + float iconAlpha = 1.0; + xoffset = 0.0; + zoffset = 0.0; + if ((BARTYPE & BITICONROW) != 0u) { + // Status/state icons: hide when camera is farther than statusFadeDistance (0 = never hide). + if (statusFadeDistance > 0.0 && camDist > statusFadeDistance) return; + // Hovering-icon row (WG.icons): shares the bars' baseline (cache[1]) and rides one row + // above the topmost bar; icons sit left-to-right by their centered slot index (v_range). + // Pulse icons fade by the shared pulseAlpha. rowSize/rowSpacing/rowOffset also drive the + // status top-band below, so the two halves of the row stay aligned. + iconHalf = BARWIDTH * rowSize; + // Rise by one bar's pitch (BAR_PITCH, in BARHEIGHT·barSpacing·barSize units to match the bar + // stack -- the row carries barSize itself since posScale doesn't apply to it) for every visible + // bar, then clear the top bar by the row's own half-height + the user offset. + // When no bars are visible, omit the ROW_GAP so the row sits tight to the unit icon. + float rowGapFactor = (dataIn[0].v_aboveBars > 0.5) ? (1.0 + ROW_GAP) : 1.0; + zoffset = -(BAR_PITCH * BARHEIGHT * barSpacing * barSize * dataIn[0].v_aboveBars + iconHalf * rowGapFactor + rowOffset); + xoffset = dataIn[0].v_rowSlot * (iconHalf * BADGE_PITCH * rowSpacing); // centered slot across states + statuses + depthbuffermod = -0.001; // same plane as the status badges (the default 0.001 sits them behind) + if ((BARTYPE & BITPULSE) != 0u) iconAlpha = pulseAlpha; + } else { + // CENTER unit icon. Hide when camera is closer than iconHideDistance (0 = never hide). + if (iconHideDistance > 0.0 && camDist < iconHideDistance) return; + // Composite rank (top-left), group number (bottom-right) and current command (bottom-left) + // onto THIS one quad (one primitive, one depth -- no z-fight, and nothing can sort between + // them). Cell indices ride the instance: v_range = rank (<0 = none); + // bartype_index .w = group, .z = command (uint, >=60000u = none). FS turns each into an origin. + iconHalf = BARWIDTH * iconSize; + clusterCells.x = dataIn[0].v_range; // rank cell (<0 = none) + clusterRankColor = dataIn[0].v_maxcolor.rgb; + uint groupCell = dataIn[0].v_bartype_index_ssboloc.w; + clusterCells.y = (groupCell < 60000u) ? float(groupCell) : -1.0; // group cell + uint cmdCell = dataIn[0].v_bartype_index_ssboloc.z; + clusterCells.z = (cmdCell < 60000u) ? float(cmdCell) : -1.0; // current-command cell + } + float iconIdx = floor(UVOFFSET + 0.5); + float col = mod(iconIdx, float(ICONATLAS_COLS)); + float row = floor(iconIdx / float(ICONATLAS_COLS)); + vec4 iconColor = vec4(dataIn[0].v_mincolor.rgb, iconAlpha); + emitRectangle( + vec4(-iconHalf, -iconHalf, iconHalf * 2.0, iconHalf * 2.0), + vec4(-iconHalf, -iconHalf, iconHalf * 2.0, iconHalf * 2.0), + BARCORNER, + 1.0, + vec4(col / float(ICONATLAS_COLS), row / float(ICONATLAS_ROWS), + 1.0 / float(ICONATLAS_COLS), 1.0 / float(ICONATLAS_ROWS)), + iconColor, + iconColor + ); + iconAtlasFlag = 0.0; + return; + } + + // Layout: all bars positioned relative to the icon center (centerpos). + // Vertical bars (left/right): centered at icon height, stacking outward. + // Horizontal bars: stacked upward from the icon center; stackIndex=0 (health) is at the bottom. + float stackIndex = float(dataIn[0].v_bartype_index_ssboloc.y); + xoffset = 0.0; + if ((BARTYPE & BITLEFT) != 0u) { + xoffset = (vbarUserX + stackIndex * BARHEIGHT * 1.2) * vbarSize; + } else if ((BARTYPE & BITRIGHT) != 0u) { + xoffset = -(vbarUserX + stackIndex * BARHEIGHT * 1.2) * vbarSize; + } + + // zoffset > 0 shifts bars downward in billboard space (pos.y - zoffset). + // Vertical bars: centered at icon level, spanning ±BARWIDTH. + // Horizontal "above" bars (damage group incl. health): stack upward from icon level, + // stackIndex=0 (health) sits right at icon center, higher indices stack further up. + // Horizontal "below" bars (build/goo/movement group): stack downward below the unit. + if ((BARTYPE & BITVERTICAL) != 0u) { + zoffset = BARWIDTH * vbarSize; + yoffset = 0.0; + } else if (isChannelBelow(UNIFORMLOC)) { + // Construction/movement group: stacks downward below the unit. (barSize applied via posScale.) + zoffset = BAR_PITCH * BARHEIGHT * barSpacing * stackIndex; + } else { + zoffset = -BAR_PITCH * BARHEIGHT * barSpacing * stackIndex; + } + + vec4 healthcolor = mix(dataIn[0].v_mincolor, dataIn[0].v_maxcolor, health); + + if ((BARTYPE & BITVERTICAL) != 0u) { + if ((BARTYPE & BITINTEGERNUMBER) != 0u) { + // INTEGER COUNT READOUT (e.g. ready stockpiled missiles): the channel value is a raw + // count (range == 1). Drawn centered on the matching gauge in the weapon column. + float count = floor(dataIn[0].v_parameters.x + 0.5); + if (count < 0.5) return; // nothing stockpiled -> no number + float bsize = BARWIDTH * rowSize; + float gap = bsize * BADGE_PITCH * rowSpacing; // matches the weapon-column gauge it labels + float colX = BARWIDTH * iconSize + bsize * 1.6 + vbarUserX; + float slot = float(dataIn[0].v_bartype_index_ssboloc.w); + xoffset = ((BARTYPE & BITLEFT) != 0u) ? -colX : colX; + zoffset = slot * gap + BARHEIGHT * 0.5; // +half glyph height to vertically center + yoffset = 0.0; + depthbuffermod = -0.004; // in front of the gauge + float halfW = BARHEIGHT * 0.5; + float gw = BARHEIGHT * 0.8; // glyph advance (matches the horizontal-bar kerning) + float ones = floor(mod(count, 10.0)); + float tens = floor(mod(count * 0.1, 10.0)); + // digit d sits at cell digitAtlasStart + (11 - d) (strip: 's','%',9..0) + if (tens != 0.0) { + emitGlyphCell(-halfW - gw * 0.5, digitAtlasStart + (11.0 - tens)); + emitGlyphCell(-halfW + gw * 0.5, digitAtlasStart + (11.0 - ones)); + } else { + emitGlyphCell(-halfW, digitAtlasStart + (11.0 - ones)); + } + iconAtlasFlag = 0.0; + return; + } + // RADIAL TIMER BADGE: the polygon's side count encodes the magnitude tier of the remaining + // time; a clockwise-from-top fill shows the fraction within that tier (sized by the tier's + // max so the fill is proportional to the real time, stepping down a shape at each boundary). + // Sources of "seconds remaining": + // - construction (BITCONSTRUCTION): build channel value bands (see updater) -> building/ + // reclaiming ETA or a constant state, colored by direction. + // - status effect (BITTIMELEFT): the channel value's overflow above 1 is the seconds. + // - weapon reload: derived from the reload fraction (v_parameters.x) and v_range. + float sides, litFrac; + if ((BARTYPE & BITGAUGE) != 0u) { + // Gauge (heat / speed / charge / teleport): the badge fills to the channel's 0..1 magnitude + // -- a level meter, not a countdown. Always a circle; color from the bartype (v_maxcolor). + // Jump charges share one value (reconstructed jumpReload, 0..charges): badge N subtracts its + // charge index (low nibble of UVOFFSET) so each fills as jumpReload passes it -- full when ready. + float chargeIdx = ((BARTYPE & BITJUMPCHARGE) != 0u) ? mod(UVOFFSET, 16.0) : 0.0; + litFrac = clamp(dataIn[0].v_parameters.x - chargeIdx, 0.0, 1.0); + sides = 1.0; + healthcolor = vec4(dataIn[0].v_maxcolor.rgb, 1.0); + } else if ((BARTYPE & (BITCONSTRUCTION | BITRATEETA)) != 0u) { + // Build channel encoding (must match the updater): [1000,..)=reclaiming (secs=v-1000), + // [2,..)=building (secs=v-2), (0,2)=constant (rate ~0). v==0 is culled before here. + float v = dataIn[0].v_parameters.x; + float secs; + bool isConstant = false; + if (v >= 2048.0) { + // Pausable ETA frame mode (goo advancing): v-2048 = completion frame /2 (mod 2048), counted + // down smoothly here. The /2 scale must match PAUSE_FRAME_SCALE in the updater. When the + // updater stops advancing it switches to the static (2+secs) band below, so the needle holds. + float rem = mod((v - 2048.0) - floor(timeInfo.x / 2.0), 2048.0) * 2.0; + secs = rem / 30.0; + healthcolor = vec4(dataIn[0].v_maxcolor.rgb, 1.0); + } else if (v >= 1000.0) { + secs = v - 1000.0; + healthcolor = vec4(1.0, 0.35, 0.2, 1.0); // reclaiming -> red/orange (construction only) + } else if (v >= 2.0) { + secs = v - 2.0; + healthcolor = vec4(dataIn[0].v_maxcolor.rgb, 1.0); // forward progress -> bartype color (green build / magenta raise) + } else { + isConstant = true; + healthcolor = vec4(0.7, 0.7, 0.7, 1.0); // constant (working, no estimate) -> grey + } + if (isConstant) { + sides = 1.0; litFrac = 1.0; // static full circle + } else { + float tier = (secs < 4.0) ? 0.0 : (secs < 16.0) ? 1.0 : (secs < 64.0) ? 2.0 : (secs < 256.0) ? 3.0 : 4.0; + sides = (tier < 0.5) ? 1.0 : (7.0 - tier); + float hi = pow(4.0, tier + 1.0); + litFrac = 1.0 - clamp(secs / hi, 0.0, 1.0); // fills clockwise as it nears completion + } + } else if ((BARTYPE & BITTIMELEFT) != 0u) { + // Status duration (paralyze/disarm/slow): when locked the channel stores the effect-END frame + // (value-101 = endFrame mod 3895, must match STATUS_LOCK_BASE/MOD in the updater) so the badge + // counts down SMOOTHLY on the GPU. value < 100 = charging / not locked -> hide. + if (dataIn[0].v_parameters.x < 100.0) return; + float secs = mod((dataIn[0].v_parameters.x - 101.0) - timeInfo.x, 3895.0) / 30.0; + if (secs <= 0.0) return; + float tier = (secs < 4.0) ? 0.0 : (secs < 16.0) ? 1.0 : (secs < 64.0) ? 2.0 : (secs < 256.0) ? 3.0 : 4.0; + sides = (tier < 0.5) ? 1.0 : (7.0 - tier); + float hi = pow(4.0, tier + 1.0); + litFrac = 1.0 - clamp(secs / hi, 0.0, 1.0); // fills clockwise as the effect runs out + } else { + float reloadSecs = dataIn[0].v_range / 30.0; // full reload duration of this weapon + bool alwaysShow = (BARTYPE & BITALWAYSSHOW) != 0u; // commanders + if (reloadSecs < reloadThreshold) { + // Too fast to bother timing: normal units hide it entirely; commanders show "ready". + if (!alwaysShow) return; + sides = 1.0; // circle + litFrac = 1.0; // full = ready (0s) + } else { + float rem = ((BARTYPE & BITINVERSE) != 0u) ? (1.0 - dataIn[0].v_parameters.x) : dataIn[0].v_parameters.x; + rem = clamp(rem, 0.0, 1.0); + float secs = rem * dataIn[0].v_range / 30.0; + // base-4 magnitude tiers, fewer sides as it gets more hopeless: circle 0-4s, hexagon + // 4-16s, pentagon 16-64s, square 64-256s, triangle 256-1024s (~17min ≈ never). + float tier = (secs < 4.0) ? 0.0 : (secs < 16.0) ? 1.0 : (secs < 64.0) ? 2.0 : (secs < 256.0) ? 3.0 : 4.0; + sides = (tier < 0.5) ? 1.0 : (7.0 - tier); // 1 -> circle in FS, else 6/5/4/3 sides + float hi = pow(4.0, tier + 1.0); // this tier's max seconds (4/16/64/256/1024) + float f = clamp(secs / hi, 0.0, 1.0); + // reload (BITINVERSE) lights up as it nears ready; status durations darken as they run out + litFrac = ((BARTYPE & BITINVERSE) != 0u) ? (1.0 - f) : f; + } + } + float bsize = BARWIDTH * rowSize; // apothem (distance to side centers); shared size for all badges + // LAYOUT ZONES (slot baked in Lua, rides v_bartype_index_ssboloc.w): + // - TOP band (status/duration: BITTIMELEFT/BITCONSTRUCTION): horizontal row above the bars. + // - WEAPON columns (BITLEFT/BITRIGHT): vertical columns flanking the unit icon. + // - BELOW (everything else, e.g. teleport/movement): pushed under the icon. + // Stable zones; only the slot within a zone changes, so badges keep recognizable positions. + float slot = float(dataIn[0].v_bartype_index_ssboloc.w); + float gap = bsize * BADGE_PITCH * rowSpacing; // ONE badge pitch for every zone (row, columns, below) + float colX = BARWIDTH * iconSize + bsize * 1.6 + vbarUserX; // column distance from icon center + if ((BARTYPE & (BITTIMELEFT | BITCONSTRUCTION)) != 0u) { + // TOP band: hide when camera is farther than statusFadeDistance (0 = never hide). + if (statusFadeDistance > 0.0 && camDist > statusFadeDistance) return; + // The same row as the hovering-icon states, riding above the topmost bar. + // Features keep a fixed slot layout (their channel meanings differ); units share the centered + // run with the states via v_rowSlot. Both use the one shared badge pitch (gap). + xoffset = (isFeature > 0.5) ? (slot * gap) : (dataIn[0].v_rowSlot * gap); + // per-bar rise matches the bar stack (BAR_PITCH, in barSize units), then clear by half a badge + // plus ROW_GAP so the row isn't glued to the top bar; rowOffset is the user fine-tune on top. + // When no bars are visible, omit the ROW_GAP so the row sits tight to the unit. + float statusGapFactor = (dataIn[0].v_aboveBars > 0.5) ? (1.0 + ROW_GAP) : 1.0; + zoffset = -(BAR_PITCH * BARHEIGHT * barSpacing * barSize * dataIn[0].v_aboveBars + bsize * statusGapFactor + rowOffset); + } else if ((BARTYPE & BITLEFT) != 0u) { + xoffset = -colX; // left weapon column, stacked down from icon level + zoffset = slot * gap; + } else if ((BARTYPE & BITRIGHT) != 0u) { + xoffset = colX; // right weapon column + zoffset = slot * gap; + } else { + // Below zone (jump charges / sprint / teleport / morph): v_rowSlot is the centered run position + // computed in the VS (persistent badges baked, morph detected live), so they spread + re-center. + xoffset = dataIn[0].v_rowSlot * gap; + zoffset = BARHEIGHT * 2.0 - belowBadgeHeight; // belowBadgeHeight raises (+) / lowers (-) + } + yoffset = 0.0; + depthbuffermod = -0.001; + // Icon (if any) is composited INTO the badge quad by the FS, so badge + icon are ONE primitive + // at ONE depth -- nothing (terrain, units, other overlay bits) can sort between them. Gauges have + // no icon (fill level + color identify them). UVOFFSET carries the icon-atlas cell for the rest. + // Non-gauge badges carry their icon cell in UVOFFSET. Gauges normally have no icon, EXCEPT jump + // charges, which show the jump command icon (UVOFFSET is taken by chargeIndex+charges*16 there, + // so the cell comes from the jumpIconCell uniform instead). + bool isJumpCharge = (BARTYPE & BITJUMPCHARGE) != 0u; + vec2 iconOrigin = vec2(0.0); + // A badge shows an icon only if it has one: UVOFFSET >= 0 is an atlas cell, -1 means "no icon" + // (iconless gauges like heat/speed, and reload badges whose weapon has no `icon` customParam) so + // the badge draws just the gauge/countdown ring. Jump charges use the jumpIconCell instead. + float hasIcon = (isJumpCharge || UVOFFSET > -0.5) ? 1.0 : 0.0; + if (hasIcon > 0.5) { + float iconIdx = isJumpCharge ? jumpIconCell : floor(UVOFFSET + 0.5); + iconOrigin = vec2(mod(iconIdx, float(ICONATLAS_COLS)) / float(ICONATLAS_COLS), + floor(iconIdx / float(ICONATLAS_COLS)) / float(ICONATLAS_ROWS)); + } + // quad is 2x the apothem so a triangle's corners (up to 2x the apothem) aren't clipped + emitRadialBadge(vec4(-bsize * 2.0, -bsize * 2.0, bsize * 4.0, bsize * 4.0), + sides, litFrac, healthcolor, iconOrigin, hasIcon); + } else { + // HORIZONTAL BAR (top/below bars): wide and short, fills left to right. + // These two knobs only touch the bars (and their numbers), not the icon/weapon overlays. + // posScale (not the shared transform) carries barSize, so the bar's local quad stays in raw + // units -- the FS's barBorderWidth math is in that raw space and must not be pre-scaled. + posScale = barSize; // size of the bar itself (+ its numbers) + // Icon->bar gap, routed THROUGH the shared transform (localLift), so it scales with the overlay + // instead of being a fixed world gap (which looked huge on small units like puppies). The transform + // scales by s = BARSCALE·overallScale·v_sizeModifier; dividing barOffset by BARSCALE here cancels + // that constant, leaving the gap = barOffset · overallScale · v_sizeModifier and keeping barOffset + // in its existing (full-size) value range. + localLift = barOffset / BARSCALE; + + // Single quad: FS decides background band / empty track / filled portion per-pixel, + // reconstructing the fill texture sample from this origin plus the pixel's fraction + // across the inset (same stretching the old separate fill quad had). + // The quad is grown by barBorderWidth on every side so the FS's matching inset + // (which shrinks back by barBorderWidth) leaves the track/fill area unchanged in + // size -- only the decorative band around it grows or shrinks. + depthbuffermod = -0.001; + // Fill comes from the runtime icon atlas: UVOFFSET is the start cell of a BARFILLCELLS-wide + // run (one row). Pass the cell's atlas origin; the FS walks the run across the bar width. + // UVOFFSET < 0 means "no fill art" -> flat color (fillUsesTexture = 0). + float fillCell = floor(UVOFFSET + 0.5); + float fillCol = mod(fillCell, float(ICONATLAS_COLS)); + float fillRow = floor(fillCell / float(ICONATLAS_COLS)); + float fillUsesTexture = (UVOFFSET >= -0.5) ? 1.0 : 0.0; + emitBarRectangle( + vec4(-BARWIDTH - barBorderWidth, -barBorderWidth, BARWIDTH * 2 + 2.0 * barBorderWidth, BARHEIGHT + 2.0 * barBorderWidth), + BARCORNER, + 1.0, // horizontal bar body (fills left to right) + health, + extraColor, + fillUsesTexture, + vec2(fillCol / float(ICONATLAS_COLS), fillRow / float(ICONATLAS_ROWS)), + healthcolor + ); + + if ((BARTYPE & BITSHOWGLYPH) != 0u) { + depthbuffermod = -0.002; + float drawPos = -BARWIDTH - BARCORNER; + // The bar's identifying glyph is gone -- the fill artwork itself identifies the bar now. + // The optional digit readout below comes from the digit strip in the runtime icon atlas: + // cell digitAtlasStart = 's', +1 = '%', then digits 9..0 (so digit d is at +(11-d)). + + if (skipGlyphsNumbers < 1.5) { + // Always a percentage readout. health = min(1, value), so a status locked at max + // (value > 1) reads as 100%; the old seconds-left countdown and its 's' glyph are gone. + float ones = floor(mod(health*100.0, 10.0)); + float tens = floor(mod(health*10.0, 10.0)); + float hundrends = floor(mod(health, 10.0)); + float unitGlyph = 1.0; // percent + + emitGlyphCell(drawPos - BARHEIGHT, digitAtlasStart + unitGlyph); + drawPos -= BARHEIGHT * 0.8; + emitGlyphCell(drawPos - BARHEIGHT, digitAtlasStart + (11.0 - ones)); + drawPos -= BARHEIGHT * 0.8; + if (tens != 0 || hundrends != 0) { + emitGlyphCell(drawPos - BARHEIGHT, digitAtlasStart + (11.0 - tens)); + } + drawPos -= BARHEIGHT * 0.8; + if (hundrends != 0) { + emitGlyphCell(drawPos - BARHEIGHT, digitAtlasStart + (11.0 - hundrends)); + } + iconAtlasFlag = 0.0; + } + } + } + +} diff --git a/LuaUI/Widgets/Shaders/UnitOverlayGL4.vert.glsl b/LuaUI/Widgets/Shaders/UnitOverlayGL4.vert.glsl new file mode 100644 index 0000000000..c0409ca924 --- /dev/null +++ b/LuaUI/Widgets/Shaders/UnitOverlayGL4.vert.glsl @@ -0,0 +1,447 @@ +#version 420 +#extension GL_ARB_uniform_buffer_object : require +#extension GL_ARB_shader_storage_buffer_object : require +#extension GL_ARB_shading_language_420pack: require + +// This file is going to be licensed under some sort of GPL-compatible license, but authors are dragging +// their feet. Avoid copying for now (unless this header rots for years on end), and check back later. +// See https://github.com/ZeroK-RTS/Zero-K/issues/5328 + +#line 5000 + +layout (location = 0) in vec4 height_timers; +#define unitHeight height_timers.x +#define sizeModifier height_timers.y +#define range height_timers.z +#define uvOffset height_timers.w +layout (location = 1) in uvec4 bartype_index_ssboloc; +layout (location = 2) in vec4 mincolor; +layout (location = 3) in vec4 maxcolor; +layout (location = 4) in uvec4 instData; + +//__ENGINEUNIFORMBUFFERDEFS__ +//__DEFINES__ + +struct SUniformsBuffer { + uint composite; // u8 drawFlag; u8 unused1; u16 id; + + uint unused2; + uint unused3; + uint unused4; + + float maxHealth; + float health; + float unused5; + float unused6; + + vec4 drawPos; + vec4 speed; + vec4[4] userDefined; //can't use float[16] because float in arrays occupies 4 * float space +}; + +layout(std140, binding=1) readonly buffer UniformsBuffer { + SUniformsBuffer uni[]; +}; + +#line 10000 + +uniform float iconDistance; +uniform float cameraDistanceMult; +uniform float cameraDistanceMultGlyph; + +out DataVS { + uint v_numvertices; + vec4 v_mincolor; + vec4 v_maxcolor; + vec4 v_centerpos; + vec4 v_uvoffsets; + vec4 v_parameters; + float v_sizeModifier; + float v_range; + float v_aboveBars; // count of visible "above" bars, so the row instances sit above the top bar + float v_rowSlot; // combined centered slot in the row above the bars (states + status badges) + float v_iconCloak; // center-icon cloak fraction 0..1 (own/allied only); fades the icon alpha + uvec4 v_bartype_index_ssboloc; +}; + +bool vertexClipped(vec4 clipspace, float tolerance) { + return any(lessThan(clipspace.xyz, -clipspace.www * tolerance)) || + any(greaterThan(clipspace.xyz, clipspace.www * tolerance)); +} +#define UNITUNIFORMS uni[instData.y] +#define UNIFORMLOC bartype_index_ssboloc.z +#define BARTYPE bartype_index_ssboloc.x + +#define BITUSEOVERLAY 1u +#define BITSHOWGLYPH 2u +#define BITPERCENTAGE 4u +#define BITTIMELEFT 8u +#define BITINTEGERNUMBER 16u +#define BITINVERSE 32u +#define BITFRAMETIME 64u +#define BITCOLORCORRECT 128u +#define BITVERTICAL 256u +#define BITLEFT 512u +#define BITRIGHT 1024u +#define BITICON 4096u +#define BITALWAYSSHOW 8192u +#define BITICONROW 16384u +#define BITPULSE 32768u +#define BITCONSTRUCTION 65536u +#define BITGAUGE 131072u +#define BITJUMPCHARGE 1048576u +#define BITICONCORNER 262144u + +// Bit-pack descriptor (generated from channelPack in gl_uniform_channels.lua). width 0 = whole float. +const int PACK_FLOAT [16] = int[16](PACK_FLOAT_INIT); +const int PACK_OFFSET[16] = int[16](PACK_OFFSET_INIT); +const int PACK_WIDTH [16] = int[16](PACK_WIDTH_INIT); +const int PACK_TYPE [16] = int[16](PACK_TYPE_INIT); + +// Every by-number channel read routes through this so packing stays consistent across value / +// visibility / centering. width 0 returns the whole float (passthrough); else extracts the sub-field +// and applies the type decode (type 1 = status -> old <=1 magnitude / >1 = 1+seconds semantics). +float readField(uint c) { + int ci = int(c); + int f = PACK_FLOAT[ci]; + float raw = UNITUNIFORMS.userDefined[f / 4][f % 4]; + int w = PACK_WIDTH[ci]; + float field = (w == 0) ? raw : mod(floor(raw / exp2(float(PACK_OFFSET[ci]))), exp2(float(w))); + if (PACK_TYPE[ci] == 1) field = (field <= 100.0) ? field * 0.01 : field - 99.0; // status + else if (PACK_TYPE[ci] == 2) field = field * 0.01; // percent (0-100 -> 0-1) + return field; +} + +float valueForChannel(uint channel) { + float value; + if (channel == 20u) { + value = 1 - UNITUNIFORMS.health / UNITUNIFORMS.maxHealth; + } else if (channel > 15) { + return 0; + } else { + value = readField(channel); + if (value < 0) { // if value is < 0, it is in relationshiop to timeInfo or gameTime. + value = -value - timeInfo.x; + value = max(0, value); + } + + value = value / range; + } + + return value; +} + +bool isVarForChannelVisible(uint channel) { + float value = valueForChannel(channel); + return value > 0.01; +} + +#define BITMODULAR 524288u +// Bar value honoring BITMODULAR: the slot holds target-frame mod 4096 (0 = ready); decode to a 0..1 +// fraction of `range` frames remaining. Non-modular falls through to the normal channel value. +float barValue(uint channel, uint bartype) { + if ((bartype & BITMODULAR) != 0u) { + float f = readField(channel); + float rem = (f < 0.5) ? 0.0 : mod(f - timeInfo.x, 4096.0); + return rem / range; + } + return valueForChannel(channel); +} +// Visibility honoring BITMODULAR: reloading iff the slot is nonzero (ready encodes 0). +bool barVisible(uint channel, uint bartype) { + // Jump charges store a (scaled, modular) frame; 0 = fully charged. A non-always-show single-charge + // badge hides when ready (like a reload); multi-charge units carry BITALWAYSSHOW so all stay visible. + if ((bartype & BITJUMPCHARGE) != 0u) return readField(channel) > 0.5; + if ((bartype & BITMODULAR) != 0u) return readField(channel) > 0.5; + return isVarForChannelVisible(channel); +} + +// Jump charges: the slot holds the (1/8-scaled, mod 4096) frame the LAST charge finishes (0 = fully +// charged). Reconstruct the continuous jumpReload (0..charges); each badge subtracts its own charge index +// in the GS. charges is packed into uvOffset (base 16) alongside the per-badge charge index. The /8 scale +// must match JUMP_FRAME_SCALE in unit_gl_uniform_updater. +float jumpChargeReload() { + float stored = readField(UNIFORMLOC); + float charges = floor(uvOffset / 16.0); + if (stored < 0.5) return charges; // sentinel: fully charged + float reloadFrames = max(range, 1.0); + float framesUntilFull = mod(stored - floor(timeInfo.x / 8.0), 4096.0) * 8.0; + return charges - framesUntilFull / reloadFrames; +} + +// Range-independent "does this channel currently render a bar" check. valueForChannel divides by the +// *current instance's* range, which is meaningless when called from the icon-row instances (their +// range encodes a layout slot, so the centre icon divides by zero). The row counts the bars it must +// clear with this instead. Mirror the bars' own visibility threshold (value/range > 0.01) so the count +// matches what actually draws -- the above-bars are all range 1, so a flat 0.01 is exact for them. +bool isChannelActive(uint channel) { + if (channel == 20u) { + return UNITUNIFORMS.maxHealth > 0.0 && (1.0 - UNITUNIFORMS.health / UNITUNIFORMS.maxHealth) > 0.01; + } + if (channel > 15u) return false; + float value = readField(channel); + if (value < 0.0) { + return (-value - timeInfo.x) > 0.0; // time-based: still in the future + } + return value > 0.01; +} + +// Goo/Morph (8) and Movement (14) stack below the unit; everything else (incl. build) stacks above. +bool isChannelBelow(uint channel) { + return channel == 8u || channel == 14u; +} + +// Number of visible "above" bars (the damage group that stacks above the unit). Mirrors the +// stacking filter below; used to lift the icon/status row clear of the topmost bar. +float countAboveBars() { + float n = 0.0; + for (uint channel = 0u; channel <= 20u; channel++) { + // Skip gadget-owned floats that aren't overlay bars: 0 = build/data, 4 = gfx-paralyze, 6 = + // selectedness, 15 = unit height (always > 0 -> would phantom-count on every unit, lifting the row). + if ((channel < 9u || channel > 12u) && channel != 4u && channel != 6u && channel != 0u && channel != 15u && + !isChannelBelow(channel) && isChannelActive(channel)) { + n += 1.0; + } + } + return n; +} + +// The top-band status badges (paralyze/disarm/slow durations + build/reclaim ETA) live in the same row +// as the hovering-icon states. They ride fixed channels, so the GPU can count which are currently drawn +// and assign each a centered slot -- exactly what the CPU does for the states -- so both halves repack +// together. Raw channel value (no range division) keeps this valid when read from any instance. +float rawChannelValue(uint channel) { + if (channel > 15u) return 0.0; + float value = readField(channel); + if (value < 0.0) value = max(0.0, -value - timeInfo.x); // time-based encoding -> seconds remaining + return value; +} + +// Slot->channel order matches the layoutSlot values baked in Lua: paralyze=0, disarm=1, slow=2. +// Mirrors the geom's actual draw test: duration badges show only once locked at max (value > 1). +bool statusBadgeDrawn(uint slot) { + if (slot == 0u) return rawChannelValue(1u) > 1.0; // paralyze + if (slot == 1u) return rawChannelValue(2u) > 1.0; // disarm + return rawChannelValue(3u) > 1.0; // slow (slot 2) +} + +float countActiveStatuses() { + float n = 0.0; + for (uint slot = 0u; slot < 3u; slot++) { if (statusBadgeDrawn(slot)) n += 1.0; } + return n; +} + +// Index of the status with the given layoutSlot among the currently-drawn status badges (for repacking). +float statusActiveIndex(uint slot) { + float idx = 0.0; + for (uint s = 0u; s < slot; s++) { if (statusBadgeDrawn(s)) idx += 1.0; } + return idx; +} + +void main() +{ +#ifdef SCREENSPACE + // In screen-space mode height_timers.xy carries screen pixel coordinates. + // The unit SSBO is still used for bar value lookup via instData.y. + if ((BARTYPE & BITALWAYSSHOW) == 0u && !barVisible(UNIFORMLOC, BARTYPE)) { v_numvertices = 0u; return; } + v_numvertices = 4u; + v_centerpos = vec4(unitHeight, sizeModifier, 0.0, 1.0); // xy = screen pixel pos + gl_Position = vec4(0.0, 0.0, 0.0, 1.0); + v_sizeModifier = 1.0; + v_aboveBars = 0.0; + v_rowSlot = 0.0; + v_parameters.y = 1.0; // always fully opaque in screen space + v_parameters.z = 1.0; + v_parameters.w = uvOffset; + v_bartype_index_ssboloc = bartype_index_ssboloc; + v_parameters.x = barValue(UNIFORMLOC, BARTYPE); + if ((BARTYPE & BITINVERSE) != 0u) v_parameters.x = 1.0 - v_parameters.x; + v_uvoffsets = vec4(0.0); + v_range = range; + v_mincolor = mincolor; + v_maxcolor = maxcolor; + return; +#endif + + vec4 drawPos = vec4(UNITUNIFORMS.drawPos.xyz, 1.0); // Models world pos and heading (.w) . Changed to use always available drawpos instead of model matrix. + + gl_Position = cameraViewProj * drawPos; // We transform this vertex into the center of the model + + v_centerpos = drawPos; // We are going to pass the centerpoint to the GS + v_numvertices = 4u; + // Always-show bars (commander reload badges) must survive even at 0 channel value so the geom + // can render their "ready" state; only off-screen clipping still culls them. + bool alwaysShow = (BARTYPE & BITALWAYSSHOW) != 0u; + if (vertexClipped(gl_Position, CLIPTOLERANCE) || ((BARTYPE & BITICON) == 0u && !alwaysShow && !barVisible(UNIFORMLOC, BARTYPE))) { + v_numvertices = 0; // Make no primitives on stuff outside of screen + return; + } + + // this sets the num prims to 0 for units further from cam than iconDistance + float cameraDistance = length((cameraViewInv[3]).xyz - v_centerpos.xyz); + + // Calculate bar alpha + v_parameters.y = (clamp(cameraDistance * cameraDistanceMult, BARFADESTART, BARFADEEND) - BARFADESTART)/ ( BARFADEEND-BARFADESTART); + v_parameters.y = 1.0 - clamp(v_parameters.y, 0.0, 1.0); + + // Calculate glyph alpha + v_parameters.z = (clamp(cameraDistance * cameraDistanceMult * cameraDistanceMultGlyph, BARFADESTART, BARFADEEND) - BARFADESTART)/ ( BARFADEEND-BARFADESTART); + v_parameters.z = 1.0 - clamp(v_parameters.z, 0.0, 1.0); + + #ifdef DEBUGSHOW + v_parameters.y = 1.0; + v_parameters.z = 1.0; + #endif + + if ((BARTYPE & BITICON) != 0u) { + v_parameters.z = v_parameters.y; // icons fade with bars, not at glyph rate + v_parameters.x = 0.0; + } + + v_parameters.w = uvOffset; + v_sizeModifier = sizeModifier; + + if (length((cameraViewInv[3]).xyz - v_centerpos.xyz) > iconDistance){ + //v_parameters.yz = vec2(0.0); // No longer needed + } + + + if (dot(v_centerpos.xyz, v_centerpos.xyz) < 1.0) v_numvertices = 0; // if the center pos is at (0,0,0) then we probably dont have the matrix yet for this unit, because it entered LOS but has not been drawn yet. + + v_centerpos.y += HEIGHTOFFSET; // Add some height to ensure above groundness + // Per-instance baseline, applied in WORLD space so the cluster sits at the unit's actual mid-body + // height (not drifting in screen space as the camera tilts). Every overlay element on a unit shares + // this same value, so they stay coplanar with each other; the in-cluster layout (bar stacking, the + // bar/row offsets) is done in billboard space by the GS. + v_centerpos.y += unitHeight; + + // This is not needed since the switch to .drawPos + //if ((UNITUNIFORMS.composite & 0x00000003u) < 1u ) v_numvertices = 0u; // this checks the drawFlag of wether the unit is actually being drawn (this is ==1 when then unit is both visible and drawn as a full model (not icon)) + + + v_bartype_index_ssboloc = bartype_index_ssboloc; + + v_bartype_index_ssboloc.y = 0; + + if ((BARTYPE & BITICON) == 0u) { + bool isWeaponBar = (BARTYPE & (BITLEFT | BITRIGHT)) != 0u; + if (isWeaponBar) { + // Weapon bars (left/right): count only bars within their channel group below current channel. + uint groupStart = ((BARTYPE & BITLEFT) != 0u) ? 9u : 11u; + for(uint channel = groupStart; channel < UNIFORMLOC; channel++) { + if (isVarForChannelVisible(channel)) { + v_bartype_index_ssboloc.y += 1; + } + } + } else { + // Horizontal bars: count visible channels above current one within the same + // above/below group (damage bars stack above the unit, build/goo/movement stack below). + bool isBelow = isChannelBelow(UNIFORMLOC); + for(uint channel = UNIFORMLOC + 1u; channel <= 20u; channel++) { + if ((channel < 9u || channel > 12u) && channel != 4u && channel != 6u && channel != 0u && channel != 15u && + isChannelBelow(channel) == isBelow && isVarForChannelVisible(channel)) { + v_bartype_index_ssboloc.y += 1; + } + } + } + + v_parameters.x = barValue(UNIFORMLOC, BARTYPE); + if ((BARTYPE & BITINVERSE) != 0u) { + v_parameters.x = 1 - v_parameters.x; + } + // Jump charges carry the reconstructed jumpReload (0..charges); the GS subtracts each badge's index. + if ((BARTYPE & BITJUMPCHARGE) != 0u) v_parameters.x = jumpChargeReload(); + } + + v_uvoffsets = vec4(0.0); + v_iconCloak = 0.0; + if (UNIFORMLOC == 20u) { + float rawParalyze = valueForChannel(1u); + float rawDisarm = valueForChannel(2u); + v_uvoffsets.x = min(1.0, rawParalyze); // paralyze overlay fraction (clamped) + v_uvoffsets.y = min(1.0, rawDisarm); // disarm overlay fraction (clamped) + v_uvoffsets.z = rawParalyze; // raw paralyze (can exceed 1.0 when stunned) + v_uvoffsets.w = rawDisarm; // raw disarm (>1 = long disarm) + } + + v_range = range; + v_mincolor = mincolor; + v_maxcolor = maxcolor; + + // Center unit icon: show white when the unit is selected (bit 23 of userDefined[0][2]). + if ((BARTYPE & BITICON) != 0u && (BARTYPE & BITICONROW) == 0u) { + float isSelected = mod(floor(UNITUNIFORMS.userDefined[0][2] / 8388608.0), 2.0); + if (isSelected > 0.5) v_mincolor.rgb = vec3(1.0); + // Status-effect magnitudes for tinting the center icon, mirroring the on-model effects + // (cus + gfx_paralyze). Carried to the FS through v_uvoffsets (unused by the icon otherwise): + // x = slow, y = disarm, z = paralyze, w = build progress (0 = none, else 0.02..1 building). + // Slow ramps with magnitude -- a partially slowed unit really is slowed. Paralyze and disarm are + // binary: the unit is only disabled at/over 100% (below that it's charging but fully functional), + // so gate those to the locked encoding (value > 1), matching gfx_paralyze on the model. + v_uvoffsets = vec4( + min(1.0, readField(3u)), // slow: continuous magnitude + (readField(2u) > 1.0) ? 1.0 : 0.0, // disarm: only once locked (>= 100%) + (readField(1u) > 1.0) ? 1.0 : 0.0, // paralyze: only once stunned (>= 100%) + readField(7u)); // build progress + + // CLOAK: cloakTime is written into the shared per-unit uniform by the CUS gadget (userDefined[3].x, + // same slot the model shader reads). Replicate the cus cloakedness ramp so the icon fades like the + // model does. The cus flags enemies as abs(cloakTime) > 1e6, so excluding that range gives the + // own/allied-only gate for free: an enemy's cloaked icon is never faded. + float cloakTime = UNITUNIFORMS.userDefined[3].x; + if (abs(cloakTime) > 0.5 && abs(cloakTime) < 1000000.0) { + if (cloakTime > 0.0) + v_iconCloak = clamp((timeInfo.x - cloakTime) / 9.0, 0.0, 1.0); // cloaking in + else + v_iconCloak = 1.0 - clamp((timeInfo.x + cloakTime) / 15.0, 0.0, 1.0); // decloaking + } + } + + // The row above the bars (hovering-icon row + top-band status badges) needs to clear the whole + // bar stack, so those instances carry the above-bar count; everyone else ignores it. + v_aboveBars = 0.0; + v_rowSlot = 0.0; + if ((BARTYPE & BITICONROW) != 0u || + ((BARTYPE & BITVERTICAL) != 0u && (BARTYPE & (BITTIMELEFT | BITCONSTRUCTION)) != 0u)) { + v_aboveBars = countAboveBars(); + // Combined centered slot for the row above the bars. The hovering-icon states come first (their + // count is computed on the CPU and delivered in userDefined[3][3]), then the GPU-counted status + // badges; everything is centered as one run so the two halves repack together. (Features ignore + // this in the geom -- their channels mean something else.) + // float 2 (userDefined[0][2]) packs slow+capture+state-count(bits 19-22)+isSelected(23); extract count. + float nStates = mod(floor(UNITUNIFORMS.userDefined[0][2] / 524288.0), 16.0); + float nStatus = countActiveStatuses(); + float total = nStates + nStatus; + if ((BARTYPE & BITICONROW) != 0u) { + // range carries this icon's raw 0-based index within the state group (baked in Lua). + v_rowSlot = range - (total - 1.0) * 0.5; + } else { + float combinedIndex = nStates + statusActiveIndex(bartype_index_ssboloc.w); + v_rowSlot = combinedIndex - (total - 1.0) * 0.5; + } + } else if ((BARTYPE & BITVERTICAL) != 0u && (BARTYPE & (BITLEFT | BITRIGHT | BITTIMELEFT | BITCONSTRUCTION)) == 0u) { + // Below-zone radial badges (jump charges, sprint, teleport, + the transient morph badge). They + // reflow as one centered run so a recon comm (morph + jump) splits left/right and a Detriment's 3 + // jump charges sit -1/0/+1. The persistent badges bake (index, count) in .w; morph rides the fixed + // channel 8, so it's detected live and slots in at the left without re-pushing the others. + uint slotPack = bartype_index_ssboloc.w; + float idx = float(slotPack & 15u); // index among the persistent below badges + float P = float((slotPack >> 4) & 15u); // count of persistent below badges (morph excluded) + bool morphActive = rawChannelValue(8u) > 0.0; + if (UNIFORMLOC == 8u) { + // morph: index 0 of the VISIBLE below run. The persistent below count P is baked, but those + // badges can HIDE (e.g. a single-charge jump once it's charged), so centering on P would leave + // the morph offset for a hidden neighbour. Count how many are actually shown from the baked + // channel mask (slotPack >> 8): a below badge is visible iff its slot is nonzero (uniform across + // modular / jump-charge / rate-ETA), so the morph re-centers as neighbours appear/disappear. + uint mask = slotPack >> 8u; + float visibleBelow = 0.0; + for (uint c = 0u; c < 16u; c++) { + if (((mask >> c) & 1u) != 0u && readField(c) > 0.5) visibleBelow += 1.0; + } + v_rowSlot = -visibleBelow * 0.5; + } else { + v_rowSlot = morphActive ? ((idx + 1.0) - P * 0.5) : (idx - (P - 1.0) * 0.5); + } + } +} diff --git a/LuaUI/Widgets/api_gadget_icons.lua b/LuaUI/Widgets/api_gadget_icons.lua index 1f76e5d0b2..35cdc50230 100644 --- a/LuaUI/Widgets/api_gadget_icons.lua +++ b/LuaUI/Widgets/api_gadget_icons.lua @@ -326,7 +326,8 @@ end function widget:Initialize() WG.icons.SetOrder('lowpower', 2) WG.icons.SetOrder('retreat', 5) - WG.icons.SetDisplay('retreat', true) + -- Visibility is owned by the Unit Overlay GL4 "Unit States" options (single source of truth); + -- this widget only pushes icon data. Pulse/order are presentation, so they stay here. WG.icons.SetPulse('retreat', true) for _, unitID in ipairs(Spring.GetAllUnits()) do diff --git a/LuaUI/Widgets/api_highlight_cus.lua b/LuaUI/Widgets/api_highlight_cus.lua index 29965ddb5b..7f4ab19325 100644 --- a/LuaUI/Widgets/api_highlight_cus.lua +++ b/LuaUI/Widgets/api_highlight_cus.lua @@ -17,9 +17,6 @@ end -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -local unitBufferUniformCache = {0} -local SELECTEDNESS_UNIFORM = 6 - local highlightPriory = {} local unitHighlights = {} @@ -32,9 +29,8 @@ function WG.SetHighlightPriority(name, priority) end local function ApplyUnitHiglight(unitID, value) - if Spring.ValidUnitID(unitID) then - unitBufferUniformCache[1] = value - gl.SetUnitBufferUniforms(unitID, unitBufferUniformCache, SELECTEDNESS_UNIFORM) + if WG.SetUnitHighlight then + WG.SetUnitHighlight(unitID, value) end end @@ -60,9 +56,8 @@ local function HighlightUnitCus(unitID, name, value) end local function HighlightFeatureCus(featureID, value) - unitBufferUniformCache[1] = value - if Spring.ValidFeatureID(featureID) then - gl.SetFeatureBufferUniforms(featureID, unitBufferUniformCache, SELECTEDNESS_UNIFORM) + if WG.SetFeatureHighlight then + WG.SetFeatureHighlight(featureID, value) end end diff --git a/LuaUI/Widgets/gfx_paralyze_effect.lua b/LuaUI/Widgets/gfx_paralyze_effect.lua index 3ed4a2b1fa..b3175ee0b8 100644 --- a/LuaUI/Widgets/gfx_paralyze_effect.lua +++ b/LuaUI/Widgets/gfx_paralyze_effect.lua @@ -625,8 +625,7 @@ function widget:GameFrame(n) val = val + 2.01 + 0.1 * disarmed end val = val + ((fire and 8) or 0) - uniformcache[1] = val - gl.SetUnitBufferUniforms(unitID, uniformcache, 4) + if WG.SetUnitParalyzeFX then WG.SetUnitParalyzeFX(unitID, val) end -- updater folds into float 4 uniformSet[unitID] = true end else @@ -649,9 +648,8 @@ function widget:UnitCreated(unitID, unitDefID) -- Enemy units might die offscreen empLinger[unitID] = nil disarmLinger[unitID] = nil - uniformcache[1] = 0 - if not uniformSet[unitID] then - gl.SetUnitBufferUniforms(unitID, uniformcache, 4) + if not uniformSet[unitID] and WG.SetUnitParalyzeFX then + WG.SetUnitParalyzeFX(unitID, 0) end local stunned = utGetIsUnitEmped(unitID) local disarmed = spGetUnitRulesParam(unitID, "disarmed") @@ -694,7 +692,7 @@ function widget:Initialize() if TESTMODE then for i, unitID in ipairs(Spring.GetAllUnits()) do widget:UnitCreated(unitID) - gl.SetUnitBufferUniforms(unitID, {1.01}, 4) + if WG.SetUnitParalyzeFX then WG.SetUnitParalyzeFX(unitID, 1.01) end end end WG['DrawParalyzedUnitGL4'] = DrawParalyzedUnitGL4 diff --git a/LuaUI/Widgets/gui_bars_screen.lua b/LuaUI/Widgets/gui_bars_screen.lua new file mode 100644 index 0000000000..e4e02d8ff4 --- /dev/null +++ b/LuaUI/Widgets/gui_bars_screen.lua @@ -0,0 +1,222 @@ +function widget:GetInfo() + return { + name = "Screen Space Bars", + desc = "Draws GL4 bars for selected units in screen space (selection panel)", + author = "Amnykon", + date = "2026", + license = "GNU GPL v2 or later", + layer = 10, + enabled = true + } +end + +-- Renders GL4 health (and later: reload, count) bars at screen-space positions +-- supplied by WG.SelectionsBarPositions. That table is populated by +-- gui_chili_selections_and_cursortip.lua when it exposes icon positions. +-- +-- Entry format in WG.SelectionsBarPositions: +-- { unitID, barname, screenX, screenY, stackIndex } +-- where screenX/Y is the center of the bar in screen pixels (Y from top). +-- +-- The same healthbar GL4 shader is reused, compiled with SCREENSPACE = 1. +-- In SCREENSPACE mode height_timers.xy = screen pixel coords (center of bar). + +-------------------------------------------------------------------------------- +-- includes +-------------------------------------------------------------------------------- + +local luaShaderDir = "LuaUI/Widgets/Include/" +local LuaShader = VFS.Include(luaShaderDir .. "LuaShader.lua") +VFS.Include(luaShaderDir .. "instancevbotable.lua") + +local includeDir = "LuaUI/Widgets/Include/" +VFS.Include(includeDir .. "gl_uniform_channels.lua") + +-------------------------------------------------------------------------------- +-- Shader config (pixels, not elmos) +-------------------------------------------------------------------------------- + +local screenBarConfig = { + SCREENSPACE = 1, + BARSCALE = 1.0, + BARWIDTH = 28, -- half-width in screen pixels + BARHEIGHT = 6, -- height in screen pixels + BARCORNER = 0.6, + SMALLERCORNER = 0.36, + MAXVERTICES = 64, + HEIGHTOFFSET = 0, + CLIPTOLERANCE = 1.2, + BGBOTTOMCOLOR = "vec4(0.25, 0.25, 0.25, 0.85)", + BGTOPCOLOR = "vec4(0.1, 0.1, 0.1, 0.85)", + BOTTOMDARKENFACTOR = 0.6, + BARFADESTART = 9999999, -- never fade in screen space + BARFADEEND = 9999999, + ATLASSTEPY = 0.03125, + ATLASSTEPX = 0.0625, + MINALPHA = 0.0, + PERCENT_VISIBILITY_MAX = 0.99, + TIMER_VISIBILITY_MIN = 0.0, + BARSTEP = 0, +} + +local barTypeMap = { + health = { + mincolor = {1.0, 0.0, 0.0, 1.0}, + maxcolor = {0.0, 1.0, 0.0, 1.0}, + bartype = 4 + 128 + 32, -- bitPercentage + bitColorCorrect + bitInverse + uniformindex = unitHealthChannel, + uvoffset = 18, + }, +} + +local vsSrcPath = "LuaUI/Widgets/Shaders/UnitOverlayGL4.vert.glsl" +local gsSrcPath = "LuaUI/Widgets/Shaders/UnitOverlayGL4.geom.glsl" +local fsSrcPath = "LuaUI/Widgets/Shaders/UnitOverlayGL4.frag.glsl" + +local shaderSourceCache = { + vssrcpath = vsSrcPath, + fssrcpath = fsSrcPath, + gssrcpath = gsSrcPath, + shaderName = "Screen Space Bars GL4", + uniformInt = { iconAtlasTex = 1 }, + uniformFloat = { + iconDistance = 0, + cameraDistanceMult = 0, + cameraDistanceMultGlyph = 0, + skipGlyphsNumbers = 2.0, -- bars only + screenWidth = 1, + screenHeight = 1, + }, + shaderConfig = screenBarConfig, +} + +-------------------------------------------------------------------------------- +-- State +-------------------------------------------------------------------------------- + +local screenBarVBO = nil +local screenBarShader = nil +local vsx, vsy + +local function goodbye(reason) + Spring.Echo("Screen Space Bars: " .. reason) + widgetHandler:RemoveWidget() +end + +local function initializeVBOTable() + local t = makeInstanceVBOTable( + { + {id = 0, name = 'height_timers', size = 4}, + {id = 1, name = 'type_index_ssboloc', size = 4, type = GL.UNSIGNED_INT}, + {id = 2, name = 'startcolor', size = 4}, + {id = 3, name = 'endcolor', size = 4}, + {id = 4, name = 'instData', size = 4, type = GL.UNSIGNED_INT}, + }, + 256, + "screenBarVBO", + 4 -- unitIDattribID + ) + if not t then goodbye("Failed to create screenBarVBO") end + local vao = gl.GetVAO() + vao:AttachVertexBuffer(t.instanceVBO) + t.VAO = vao + return t +end + +-------------------------------------------------------------------------------- +-- VBO update helpers +-------------------------------------------------------------------------------- + +local cache = {} +for i = 1, 20 do cache[i] = 0 end + +local function pushBar(unitID, barname, screenX, screenY, stackIndex) + local bt = barTypeMap[barname] + if not bt then return end + local instanceID = unitID .. "_ss_" .. barname + cache[1] = screenX -- height_timers.x: screen pixel X + cache[2] = screenY -- height_timers.y: screen pixel Y (from top) + cache[3] = 1 -- range + cache[4] = bt.uvoffset -- uvOffset + + cache[5] = bt.bartype -- bartype_index_ssboloc.x + cache[6] = stackIndex or 0 -- stacking index + cache[7] = bt.uniformindex -- uniformindex (SSBO channel) + cache[8] = 0 + + cache[9] = bt.mincolor[1]; cache[10] = bt.mincolor[2] + cache[11] = bt.mincolor[3]; cache[12] = bt.mincolor[4] + cache[13] = bt.maxcolor[1]; cache[14] = bt.maxcolor[2] + cache[15] = bt.maxcolor[3]; cache[16] = bt.maxcolor[4] + + pushElementInstance(screenBarVBO, cache, instanceID, true, nil, unitID) +end + +local function clearBars() + clearInstanceTable(screenBarVBO) +end + +-------------------------------------------------------------------------------- +-- Rebuild bars from WG.SelectionsBarPositions +-------------------------------------------------------------------------------- + +local function rebuildBars() + clearBars() + local positions = WG.SelectionsBarPositions + if not positions then return end + for i = 1, #positions do + local entry = positions[i] + pushBar(entry[1], entry[2], entry[3], entry[4], entry[5] or 0) + end +end + +-------------------------------------------------------------------------------- +-- Widget callbacks +-------------------------------------------------------------------------------- + +function widget:DrawScreen() + if not screenBarVBO then return end + local positions = WG.SelectionsBarPositions + if not positions or #positions == 0 then return end + + -- Rebuild VBO from WG table each frame (positions can shift with Chili layout). + rebuildBars() + if screenBarVBO.usedElements == 0 then return end + + gl.Blending(GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA) + -- TODO (Phase 4): bar fills now sample the runtime icon atlas (unit 1). That atlas is built by + -- the world-space widget (gui_unit_overlay_gl4); share it in (e.g. via WG) and bind it here. + if WG.UnitOverlayIconAtlas then gl.Texture(1, WG.UnitOverlayIconAtlas) end + screenBarShader:Activate() + screenBarShader:SetUniform("screenWidth", vsx) + screenBarShader:SetUniform("screenHeight", vsy) + screenBarShader:SetUniform("skipGlyphsNumbers", 2.0) + + screenBarVBO.VAO:DrawArrays(GL.POINTS, screenBarVBO.usedElements) + + screenBarShader:Deactivate() + gl.Texture(1, false) + gl.Blending(false) +end + +function widget:ViewResize(newVsx, newVsy) + vsx, vsy = newVsx, newVsy +end + +function widget:Initialize() + vsx, vsy = Spring.GetViewGeometry() + + screenBarShader = LuaShader.CheckShaderUpdates(shaderSourceCache) + if not screenBarShader then goodbye("Failed to compile screen space bars shader") return end + + screenBarVBO = initializeVBOTable() + if not screenBarVBO then return end + + WG.SelectionsBarPositions = WG.SelectionsBarPositions or {} +end + +function widget:Shutdown() + if screenBarVBO then + clearInstanceTable(screenBarVBO) + end +end diff --git a/LuaUI/Widgets/gui_chili_selections_and_cursortip.lua b/LuaUI/Widgets/gui_chili_selections_and_cursortip.lua index dbd9d7d702..29d106b400 100644 --- a/LuaUI/Widgets/gui_chili_selections_and_cursortip.lua +++ b/LuaUI/Widgets/gui_chili_selections_and_cursortip.lua @@ -1660,6 +1660,17 @@ local function GetUnitGroupIconButton(parentControl) unitImage:Invalidate() end + function externalStuff.GetUnitID() + return unitID + end + + function externalStuff.GetScreenPos() + if not externalStuff.visible then return nil end + local sx, sy = holder:UnscaledLocalToScreen(0, 0) + local scale = WG.uiScale or 1 + return sx * scale, sy * scale + end + return externalStuff end @@ -2004,6 +2015,24 @@ local function GetMultiUnitInfoPanel(parentControl) end end + function externalFunctions.GetBarPositions() + local positions = {} + for i = 1, #displayButtons do + local btn = displayButtons[i] + if btn.visible then + local uid = btn.GetUnitID() + if uid then + local sx, sy = btn.GetScreenPos() + if sx then + -- Center X on icon, bar at 85% down the icon height. + positions[#positions + 1] = {uid, "health", sx + iconSize * 0.5, sy + iconSize * 0.85, 0} + end + end + end + end + return positions + end + return externalFunctions end @@ -2117,6 +2146,7 @@ local function GetSingleUnitInfoPanel(parentControl, isTooltipVersion) end local prevUnitID, prevUnitDefID, prevFeatureID, prevFeatureDefID, prevVisible, prevMorphTime, prevMorphCost, prevMousePlace + local currentHealthPos = PIC_HEIGHT + 4 local externalFunctions = {} local function UpdateReloadTime(unitID, unitDefID) @@ -2185,7 +2215,8 @@ local function GetSingleUnitInfoPanel(parentControl, isTooltipVersion) unitpicBadgeUpdate(GetUnitNeedRearm(unitID, unitDefID), IMAGE.NO_AMMO) UpdateReloadTime(unitID, unitDefID) - + if healthPos then currentHealthPos = healthPos end + return showMetalInfo end @@ -2451,7 +2482,16 @@ local function GetSingleUnitInfoPanel(parentControl, isTooltipVersion) leftPanel:SetVisibility(newVisible) rightPanel:SetVisibility(newVisible) end - + + function externalFunctions.GetBarPositions() + if not selectedUnitID then return {} end + local barCenterX = ICON_SIZE + 3 + (RIGHT_WIDTH - ICON_SIZE - 3) * 0.5 + local barCenterY = currentHealthPos + BAR_SIZE * 0.5 + local sx, sy = rightPanel:UnscaledLocalToScreen(barCenterX, barCenterY) + local scale = WG.uiScale or 1 + return {{selectedUnitID, "health", sx * scale, sy * scale, 0}} + end + return externalFunctions end @@ -2809,17 +2849,20 @@ local function GetSelectionWindow() singleUnitDisplay.SetVisible(true) multiUnitDisplay.SetUnitDisplay() selectionStatsDisplay.ChangeSelection({unitID}) + WG.SelectionsBarPositions = {} -- positions unavailable until next UpdateSelectionWindow end - + function externalFunctions.ShowMultiUnit(newSelection) singleUnitID = nil multiUnitDisplay.SetUnitDisplay(newSelection) singleUnitDisplay.SetVisible(false) selectionStatsDisplay.ChangeSelection(newSelection) + WG.SelectionsBarPositions = {} end function externalFunctions.UpdateSelectionWindow() if not visible then + WG.SelectionsBarPositions = {} return end if singleUnitID then @@ -2828,11 +2871,18 @@ local function GetSelectionWindow() multiUnitDisplay.UpdateUnitDisplay() end selectionStatsDisplay.UpdateStats() + -- Export icon screen positions for the GL4 screen-space bars widget. + if singleUnitID then + WG.SelectionsBarPositions = singleUnitDisplay.GetBarPositions() + else + WG.SelectionsBarPositions = multiUnitDisplay.GetBarPositions() + end end - + function externalFunctions.SetVisible(newVisible) if not newVisible then singleUnitID = nil + WG.SelectionsBarPositions = {} end visible = newVisible mainPanel:SetVisibility(newVisible) @@ -2996,6 +3046,7 @@ function widget:Initialize() drawHotkeyBytes[drawHotkeyBytesCount] = v:byte(-1) end + WG.SelectionsBarPositions = {} selectionWindow = GetSelectionWindow() tooltipWindow = (WG.Modding_TooltipOverride and WG.Modding_TooltipOverride()) or GetTooltipWindow() InitializeWindParameters() diff --git a/LuaUI/Widgets/gui_unit_overlay_gl4.lua b/LuaUI/Widgets/gui_unit_overlay_gl4.lua new file mode 100644 index 0000000000..fc7863acae --- /dev/null +++ b/LuaUI/Widgets/gui_unit_overlay_gl4.lua @@ -0,0 +1,2415 @@ +function widget:GetInfo() + return { + name = "Unit Overlay GL4", + desc = "GL4 unit overlay: health bars, weapon bars, status icons", + author = "Beherith", + date = "October 2019", + license = "GNU GPL, v2 or later for Lua code, (c) Beherith (mysterme@gmail.com) for GLSL", + layer = -10, + enabled = true + } +end + +local init -- forward declaration so option OnChange handlers can re-add bars +options_path = 'Settings/Interface/Unit Overlay' +local layout_path = options_path .. '/Size & Layout' +options_order = { + -- General + 'showGlyphsNumbers', 'drawFeatureHealth', 'fadeDistance', 'statusFadeDistance', 'iconHideDistance', 'trackDarken', 'reloadThreshold', + 'debugDrawAtlas', + -- Size & Layout (nested) + 'overallScale', + 'barSize', 'barSpacing', 'barHeightAboveUnit', 'barBorder', + 'statusSize', 'statusSpacing', 'statusHeight', + 'unitIconSize', 'weaponBarSize', 'weaponBarOffset', 'abilityBadgeHeight', +} +options = { + showGlyphsNumbers = { + name = 'Bar detail', + type = 'select', + value = 'Icons and numbers', + items = {'Icons and numbers', 'Numbers only', 'Bars only'}, + noHotkey = true, + desc = 'How much detail to show on bars: icons and numbers, just numbers, or plain bars.', + OnChange = function(self) + local map = {['Icons and numbers'] = 0.0, ['Numbers only'] = 1.0, ['Bars only'] = 2.0} + skipGlyphsNumbers = map[self.value] or 0.0 + end + }, + drawFeatureHealth = { + name = 'Show health on wrecks', + type = 'bool', + value = false, + noHotkey = true, + desc = 'Show health bars on wrecks and corpses.', + OnChange = function() + initfeaturebars() + end + }, + fadeDistance = { + name = 'Fade-out distance', + type = 'number', value = 3200, min = 0, max = 20000, step = 100, + noHotkey = true, + desc = 'Camera distance at which bars start fading out. 0 = never fade.', + }, + statusFadeDistance = { + name = 'Status icons hide distance', + type = 'number', value = 0, min = 0, max = 20000, step = 100, + noHotkey = true, + desc = 'Camera distance beyond which status/state icons above bars are hidden. 0 = never hide.', + }, + iconHideDistance = { + name = 'Unit icon hide distance', + type = 'number', value = 0, min = 0, max = 2000, step = 50, + noHotkey = true, + desc = 'Camera distance below which the center unit type icon is hidden. 0 = never hide.', + }, + trackDarken = { + name = 'Empty bar brightness', + type = 'number', value = 0.25, min = 0.0, max = 1.0, step = 0.05, + noHotkey = true, desc = 'Brightness of the empty part of a bar (1 = as bright as the filled part, lower = darker).', + }, + reloadThreshold = { + name = 'Hide quick reload timers under', + type = 'number', value = 2.0, min = 0.1, max = 15.0, step = 0.1, + noHotkey = true, + desc = 'Weapons that reload faster than this (seconds) do not show a reload timer. Commanders always show one.', + OnChange = function() init() end, + }, + debugDrawAtlas = { + name = 'DEBUG: show icon atlas', + type = 'bool', + value = false, + noHotkey = true, + desc = 'Draw the runtime icon atlas in the center of the screen (for debugging atlas/cell issues).', + }, + + -- Size & Layout -------------------------------------------------------------------------------- + overallScale = { + name = 'Overall overlay scale', + path = layout_path, + type = 'number', value = 1, min = 0.5, max = 2, step = 0.05, + noHotkey = true, desc = 'Scales the whole overlay (bars, badges, icons) uniformly, alongside the per-unit size multiplier.', + }, + barSize = { + name = 'Bar size', + path = layout_path, + type = 'number', value = 1, min = 0.3, max = 3, step = 0.05, + noHotkey = true, desc = 'Size of the health and status bars (and their numbers).', + }, + barSpacing = { + name = 'Bar spacing', + path = layout_path, + type = 'number', value = 1, min = 0.3, max = 3, step = 0.05, + noHotkey = true, desc = 'Gap between stacked bars.', + }, + barHeightAboveUnit = { + name = 'Bar height above unit', + path = layout_path, + type = 'number', value = 0, min = -50, max = 50, step = 1, + noHotkey = true, desc = 'Raise or lower the bars relative to the unit.', + }, + barBorder = { + name = 'Bar border thickness', + path = layout_path, + type = 'number', value = 0.25, min = 0.0, max = 1.0, step = 0.02, + noHotkey = true, desc = 'Thickness of the decorative border around each bar.', + }, + statusSize = { + name = 'Icon & badge size', + path = layout_path, + type = 'number', value = 1.0, min = 0.2, max = 4.0, step = 0.1, + noHotkey = true, desc = 'Size of the state icons, status-effect badges and weapon reload badges.', + }, + statusSpacing = { + name = 'Status row spacing', + path = layout_path, + type = 'number', value = 1.0, min = 0.2, max = 4.0, step = 0.1, + noHotkey = true, desc = 'Spacing between items in the row of states and status effects above the bars.', + }, + statusHeight = { + name = 'Status row height', + path = layout_path, + type = 'number', value = 0.0, min = -30, max = 30, step = 0.1, + noHotkey = true, desc = 'Raise or lower the row of states and status effects above the bars.', + }, + unitIconSize = { + name = 'Unit icon size', + path = layout_path, + type = 'number', value = 1.0, min = 0.1, max = 4, step = 0.1, + noHotkey = true, desc = 'Size of the unit icon.', + }, + weaponBarSize = { + name = 'Weapon bar size', + path = layout_path, + type = 'number', value = 1, min = 0.1, max = 4, step = 0.1, + noHotkey = true, desc = 'Size of the vertical weapon reload bars beside the unit.', + }, + weaponBarOffset = { + name = 'Weapon bar offset', + path = layout_path, + type = 'number', value = 0, min = -20, max = 20, step = 0.25, + noHotkey = true, desc = 'How far the weapon bars sit out to the sides of the unit.', + }, + abilityBadgeHeight = { + name = 'Ability badge height', + path = layout_path, + type = 'number', value = 0.0, min = -30, max = 30, step = 0.1, + noHotkey = true, desc = 'Raise or lower the ability badges (jump, morph, teleport) below the unit.', + }, +} + +-- Unit-state icons (WG.icons). These options are the single source of truth for state-icon +-- visibility: the producer widgets (State Icons, Gadget Icons, Rank Icons) only push icon data; +-- this widget decides what is shown via WG.icons.SetDisplay. stateCtl.apply* are defined once +-- WG.icons exists (further down); the OnChange handlers call through stateCtl so the closures don't +-- need WG.icons at parse time. +local states_path = options_path .. '/Unit States' +local stateCtl = {} -- { apply(name), applyAll(), refreshShift() } -- assigned after WG.icons below +local stateIconBool = { + { name = 'rank', label = 'Veterancy rank', default = true }, + { name = 'group', label = 'Control group number', default = true }, + { name = 'lowpower', label = 'Low power (no energy)', default = true }, + { name = 'facplop', label = 'Factory plate to place', default = true }, + { name = 'nofactory', label = 'Plate without factory', default = true }, + { name = 'retreat', label = 'Retreating', default = true }, + { name = 'wait', label = 'Waiting', default = true }, + { name = 'padExclude', label = 'Excluded from pad', default = true }, + { name = 'rearm', label = 'Out of ammo / rearming', default = false }, +} +local stateIconTri = { + { name = 'armored', label = 'Armored', default = 'shift' }, + { name = 'priority', label = 'Build priority', default = 'shift' }, + { name = 'firestate', label = 'Fire state', default = 'shift' }, + { name = 'movestate', label = 'Move state', default = 'shift' }, + { name = 'miscpriority', label = 'Misc priority (morph/stockpile)', default = 'shift' }, + { name = 'command', label = 'Current command', default = 'shift' }, +} +for _, s in ipairs(stateIconBool) do + local key = 'state_' .. s.name + options[key] = { + name = s.label, path = states_path, type = 'bool', value = s.default, noHotkey = true, + OnChange = function() stateCtl.apply(s.name) end, + } + options_order[#options_order + 1] = key +end +for _, s in ipairs(stateIconTri) do + local key = 'state_' .. s.name + options[key] = { + name = s.label, path = states_path, type = 'radioButton', value = s.default, noHotkey = true, + items = { {key='always', name='Always'}, {key='shift', name='When holding Shift'}, {key='never', name='Never'} }, + OnChange = function() stateCtl.apply(s.name) end, + } + options_order[#options_order + 1] = key +end + +-- wellity wellity the time has come, and yes, this is design documentation +-- what can we do with 64 verts per healthbars? + -- 9 verts bg + -- 9 verts fg + -- 20 verts for numbers like an asshole +-- fade bars in and out based on last modified times of values? +-- what info do we need outputted from GS? +-- for fg/bg +-- color? is that it? + +-- for numbers: +-- uv coords +-- we also need one extra for text - no bueno for translations tho + +-- use billboards, +-- THE TYPES OF UNIT BARS: + -- timer based, all these need a start and (predicted) end time. + -- EMP time left + -- 3 floats, start, end, empdamage + -- needs update on every fucking unitdamaged callin + -- handle cases where uni is empd outside of view? + + -- reload + -- 2 floats, lastshot, nextshot + -- time left in construction + -- this is a special hybrid bar added on unitcreated, and removed on unitfinished... + -- 2 floats, buildpct, eta? (eta could get liveupdated cause unitfinished?) + -- static percentage based: + -- health -- + -- emp damage + -- capture + -- stockpile build progress + -- shield + +-- stuff that needs to occupy a contiguouis stretch in the user uniforms: + +-- Spring.GetUnitHealth ( number unitID ) +-- return: nil | number health, number maxHealth, number paralyzeDamage, number captureProgress, number buildProgress + +-- local shieldOn, shieldPower = GetUnitShieldState(unitID) +-- numStockpiled, numStockpileQued, stockpileBuild = GetUnitStockpile(unitID) +-- local stunned = GetUnitIsStunned(unitID) +-- local _, reloaded, reloadFrame = GetUnitWeaponState(unitID, ci.primaryWeapon) + +-- Features can only have: Health, reclaim and resurrectprogress - in fact they should be completely separate bar ids, and all of them are static percentage based + -- feature resurrect -- this list must be handled in-widget, maintained and updated accordingly for in-los features. + -- advanced concepts include priority watch lists of features actively being resurrected (or hooking into allowcommand, but that is garbage!) + + -- feature health + -- feature reclaim +-- AllowFeatureBuildStep() called when wreck is resurrected + +-- Spring.GetFeatureHealth ( number featureID ) +--return: nil | number health, number maxHealth, number resurrectProgress +--Spring.GetFeatureResources ( number featureID ) +--return: nil | number RemainingMetal, number maxMetal, number RemainingEnergy, number maxEnergy, number reclaimLeft, number reclaimTime + +-- the vertex shader: + -- Job of the VS: + -- read the data and position + -- identify if the bar needs to be drawn based on : + -- visibility of unit + -- distance of bar + -- value of the bar + -- the colormap of the bar needs to be interpolated here from a fixed define string? + --[[ -- https://community.khronos.org/t/constant-vec3-array-no-go/60184/8 + vec3 MyArray[4]=vec3[4]( + vec3(1.5,34.4,3.2), + vec3(1.6,34.1,1.2), + vec3(18.981777,6.258294,-27.141813), + vec3(1.0,3.0,1.0) + ); + ]]-- + -- + -- VS input: + -- uint barindex + -- this is the index of how manyeth bar it is in the list, where 0 is always health. and if an additional bar is needed, then increment accordingly + -- uint bartype + -- this is for where to get the colortable and 'icon' from + -- float unitheight + -- for correct offsetting + -- uint uniformSSBOloc + -- this is what uniform offset to read, 0 will be health? + -- float2 timers + -- this is for setting the time from which to calculate the timer based bars, set to 0 for no timer, start and end time maybe to calc diff? + -- uint unitID + -- or a featureID for features, those will be a separate list, but use hopefully the same shader. + -- + -- VS output + -- unit position + -- bar position + -- bar 'scale' + -- bar basecolor + -- bar colormap vec3[3] + -- bar value + -- bar type + -- bar alpha + -- corner size + +-- Geometry shader: + -- should only output anything if the bar actually needs to be drawn +-- Job of the geometry shader: + -- take the VS output params, and create the following bar components: + -- At furthest detail: + -- background which is same size as bar + -- the bar itself + -- 2*4 vertices + -- midrange: + -- a nicer 6 triangle cornered bar background + -- a cornered bar foreground + -- 2*8 vertices + -- closeup: + -- add the percentage value to the left of the bar + -- this is 4*4 vertices + -- full closeness + -- also write the 'name' of the bar type + -- GS output per vertex: + -- position on screen + -- Z depth (somehow with emission ordering from back to front? + -- UV coordinates -- this could get nasty quickly + -- vertex color + -- solid or textured + +-- Fragment shader: + -- if solid, interpolate vertex color, and straight up draw it + -- if uv mapped, sample the texture and draw it + +-- atlas plans: + -- 512 x 512 atlas + -- 16 rows in it + -- each number from 0 to 9, '.' % and space (the 15th.) 's', ':' + -- the text? + -- overlay textures for bars + -- symbol glyphs + +-- TODO +-- 1. enemy paralyzed is not visible? +-- enemy comms and fusions health? hide the ones which should be hidden! +-- check for invalidness on addbars -- dont +-- better maintenance of bartypes and watch lists +-- feature bars fade out faster -- done +-- CLOAKED UNITSES -- done +-- Healthbars color correction -- done +-- Hide buildbars when at full hp - or convert them to build bars? -- done +-- todo some tex filtering issues on healthbar tops and bottoms :/ -- done +-- TODO: some GAIA shit? -- done +-- TODO: enemy comms and fus and decoy fus should not get healthbars! -- done +-- TODO: allies dont get reload bars? Do Specs see them? -- done (it was f'ed up previously) +-- TODO: correct draw order (after highlightunit) -- done +-- TODO: when reiniting feature bars, also check for resurrect/reclaim status -- done, just dont reinit them on playerchanged, no point! + -- now this is problematic, as the gadget only sends us an event on first reclaim event + -- we must assume that all features + -- feature bars dont actually need a reinit, now do they? +-- TODO: make numbers, glyphs optional? -- done, but untested + +--/luarules fightertest corak armpw 100 10 2000 + +local drawWhenGuiHidden = false + +-- a little explanation for 'bartype' +-- 0: default percentage progress bar +-- 1: timer based full textured bar, with time left being read from unitformindex +-- 2: timer based progress bar, with start and end times reading time left from uniformindex, uniformindex + 1 and timeInfo.x +-- 3: default percentage bar with overlayed texture progression +-- 5: The stockpile bar, nasty as hell but whatevs, it + +-- TODO: should be a freaking bitmask instead +-- bit 0: use overlay texture false/true +-- bit 1: show glyph icon +-- bit 2: use percentage style display +-- bit 3: use timeleft style display (2 and 3 mutually exclusive!) +-- bit 4: use integernumber style display (stockpile) +-- bit 5: get progress from nowtime-uniform2 / (uniform3 - uniform2) +-- bit 6: flash bar at 1hz +local bitUseOverlay = 1 +local bitShowGlyph = 2 +local bitPercentage = 4 +local bitTimeLeft = 8 +local bitIntegerNumber = 16 +local bitInverse = 32 +local bitFrameTime = 64 +local bitColorCorrect = 128 +local bitVertical = 256 -- bar fills bottom-to-top +local bitLeft = 512 -- position bar left of unit (primary weapon) +local bitRight = 1024 -- position bar right of unit (secondary weapon) +local bitIcon = 4096 -- draw unit icon billboard +local bitAlwaysShow = 8192 -- radial badge: always render (commanders), showing "ready" when below the reload threshold +local bitIconRow = 16384 -- hovering-icon row (WG.icons): billboard icon placed by centered slot index +local bitPulse = 32768 -- hovering icon flashes (alpha oscillates via pulseAlpha) +local bitConstruction = 65536 -- radial badge driven by the build channel's duration encoding (building/reclaiming/constant) +local bitGauge = 131072 -- radial badge that fills to a 0..1 magnitude (heat/speed/charge), not a countdown +local bitIconCorner = 262144 -- icon billboard pinned to a corner of the unit icon (rank TL / group number BR) +local bitModular = 524288 -- ability-slot duration bar: value is target-frame mod 4096, GPU-decremented +local bitJumpCharge = 1048576 -- below-zone gauge whose value is a reconstructed jumpReload; each badge shows one charge +local bitRateETA = 2097152 -- below-zone radial ETA badge: build-style band decode (0 hidden/1 paused/2+secs) but NOT top-band + +-- Columns in the vertical (weapon bar) glyph atlas. Distinct from the horizontal +-- glyph atlas's uvoffset numbering -- these bar types are always BITVERTICAL, so +-- their uvoffset only ever feeds the vertical atlas lookup in the geom shader. +local VBAR_COL_ENERGY = 0 +local VBAR_COL_KINETIC = 1 +local VBAR_COL_EXPLOSIVE = 2 +local VBAR_COL_BURST = 3 +local VBAR_COL_GENERIC_RELOAD = 4 -- fallback when a unit's weapon has no weapon_class +local VBAR_COL_HEAT = 5 +local VBAR_COL_DGUN = 6 +local VBAR_COL_TELEPORT = 7 +local VBAR_COL_SPEED = 8 +local VBAR_COL_STOCKPILE = 9 +local VBAR_COL_REAMMO = 10 +local VBAR_COL_CAPTURERELOAD = 11 +local VBAR_COL_LIGHTNING = 12 +local VBAR_COL_FLAME = 13 + +local weaponClassVbarColumn = { + energy = VBAR_COL_ENERGY, + kinetic = VBAR_COL_KINETIC, + explosive = VBAR_COL_EXPLOSIVE, + burst = VBAR_COL_BURST, + lightning = VBAR_COL_LIGHTNING, + flame = VBAR_COL_FLAME, +} + +local includeDir = "LuaUI/Widgets/Include/" +VFS.Include(includeDir.."gl_uniform_channels.lua") + +local icontypes = VFS.FileExists(LUAUI_DIRNAME .. "Configs/icontypes.lua") and VFS.Include(LUAUI_DIRNAME .. "Configs/icontypes.lua") or {} +local _, iconFormat = VFS.Include(LUAUI_DIRNAME .. "Configs/chilitip_conf.lua", nil, VFS.ZIP) +iconFormat = iconFormat or ".dds" +local iconAtlasTexture = nil +local unitDefIconIndex = {} +-- Reload-badge icons now come from each weapon's `icon` customParam (a path), registered on demand via +-- registerDynamicIcon; weapon-class/commweapon icon tables are retired. +-- numbers.png is a 12-glyph strip "s % 9 8 7 6 5 4 3 2 1 0" (64px each), blitted across 12 +-- contiguous atlas cells. digitAtlasIndex maps a digit 0-9 (and 's'/'%') to its atlas index. +local digitStripPath = "LuaUI/Images/numbers.png" +local digitStripGlyphs = { 's', '%', 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 } -- left-to-right order in the strip +local digitAtlasIndex = {} +local digitAtlasStartIndex = 0 -- atlas cell of the strip's first glyph ('s'); fed to the shader as digitAtlasStart +-- Horizontal fill bars: each is a 576x64 png = 9 contiguous 64px atlas cells. barAtlasIndex maps +-- a bar name to its starting cell; the shader samples the 9-cell span across the bar's width. +local barFillCells = 9 -- 576 / 64 +local barAtlasFills = { "build", "capture", "shield", "slow", "disarm", "disable", "health", "reclaim", "resurrect" } +local barAtlasIndex = {} +-- Status-duration badge icons (the Bold command icons), keyed by status name -> atlas cell. +local statusIconImage = { paralyze = "disable", disarm = "disarm", slow = "slow", build = "build", resurrect = "resurrect", + morph = "upgrade", stockpile = "missile", teleport = "drop_beacon", ability = "sprint", + rearm = "rearm", goo = "reclaim" } +local statusIconIndex = {} +-- Jump-charge gauge badges composite this command icon (atlas cell fed to the shader as jumpIconCell). +local jumpIconPath = "LuaUI/Images/commands/Bold/jump.png" +local jumpIconAtlasIndex = 0 +local iconAtlasCols = 16 +-- One 9-cell bar per row leaves 7 cells unused, so the bars alone need ~9 rows on top of the +-- ~200 unit icon-type cells + the digit row; 16 rows can't hold all that, so the atlas is 32 tall. +local iconAtlasRows = 32 +local iconAtlasCellSize = 64 + +local barTypeMap = { + health = { + mincolor = {1.0, 0.0, 0.0, 1.0}, + maxcolor = {0.0, 1.0, 0.0, 1.0}, + bartype = bitPercentage + bitColorCorrect + bitInverse, + hidethreshold = 0.99, + uniformindex = unitHealthChannel, + uvoffset = 18, + fill = "health", + }, + paralyze = { + -- white so the pre-colored fill art (disable.png) shows as authored (fill is color-multiplied) + mincolor = {1.0, 1.0, 1.0, 1.0}, + maxcolor = {1.0, 1.0, 1.0, 1.0}, + bartype = bitShowGlyph + bitUseOverlay + bitPercentage, + hidethreshold = 1.99, + uniformindex = unitParalyzeChannel, + uvoffset = 19, + fill = "disable", + }, + build = { + -- Construction/reclaim progress fill bar (ch7 = buildProgress 0..1 while building/reclaiming, + -- 0 when finished). White so the pre-colored fill art (build.png) shows as authored. + mincolor = {1.0, 1.0, 1.0, 1.0}, + maxcolor = {1.0, 1.0, 1.0, 1.0}, + bartype = bitShowGlyph + bitPercentage, + hidethreshold = 0.999, + uniformindex = unitBuildChannel, + uvoffset = 2, + fill = "build", + }, + morph = { -- non-weapon ability -> below zone. rate-ETA (build-style): counts down to completion, + -- grey static when stalled (insufficient resources). Updater encodes the band on float 8. + mincolor = {0.80, 0.30, 1.00, 1.0}, + maxcolor = {0.80, 0.30, 1.00, 1.0}, -- magenta (forward-progress band color) + bartype = bitVertical + bitRateETA, + hidethreshold = 0.99, + uniformindex = unitMorphChannel, + uvoffset = 0, + statusIcon = "morph", -- upgrade symbol + }, + disarm = { + -- white so the pre-colored fill art (disarm.png) shows as authored (fill is color-multiplied) + mincolor = {1.0, 1.0, 1.0, 1.0}, + maxcolor = {1.0, 1.0, 1.0, 1.0}, + bartype = bitShowGlyph + bitUseOverlay + bitPercentage, + hidethreshold = 0.99, + uniformindex = unitDisarmChannel, + uvoffset = 15, + fill = "disarm", + }, + slow = { + -- white so the pre-colored fill art (slow.png) shows as authored (fill is color-multiplied) + mincolor = {1.0, 1.0, 1.0, 1.0}, + maxcolor = {1.0, 1.0, 1.0, 1.0}, + bartype = bitPercentage + bitColorCorrect, + hidethreshold = 0.99, + uniformindex = unitSlowChannel, + uvoffset = 16, + fill = "slow", + }, + -- Status-duration radial badges: read the same channel as the damage bar, but show only the + -- "locked at max" duration (the channel value's >1 overflow, in seconds). bitTimeLeft selects the + -- status-duration mode in the shader; uvoffset (the badge icon) is set after the atlas is built. + paralyzetimer = { + mincolor = {0.6, 0.6, 1.0, 1.0}, maxcolor = {0.6, 0.6, 1.0, 1.0}, + bartype = bitVertical + bitTimeLeft, + hidethreshold = 0.99, + uniformindex = unitParalyzeChannel, + uvoffset = 0, statusIcon = "paralyze", layoutSlot = 0, -- top band + }, + disarmtimer = { + mincolor = {0.6, 0.6, 1.0, 1.0}, maxcolor = {0.6, 0.6, 1.0, 1.0}, + bartype = bitVertical + bitTimeLeft, + hidethreshold = 0.99, + uniformindex = unitDisarmChannel, + uvoffset = 0, statusIcon = "disarm", layoutSlot = 1, + }, + slowtimer = { + mincolor = {0.4, 0.6, 1.0, 1.0}, maxcolor = {0.4, 0.6, 1.0, 1.0}, + bartype = bitVertical + bitTimeLeft, + hidethreshold = 0.99, + uniformindex = unitSlowChannel, + uvoffset = 0, statusIcon = "slow", layoutSlot = 2, + }, + -- Weapon cooldown badges flank the icon in 2 columns: primary(1)/tertiary(3) left, secondary(2)/ + -- quaternary(4) right, higher priority on top (layoutSlot = row within the column). + reload = { -- weapon 1: left, top + mincolor = {0.03, 0.4, 0.4, 1.0}, + maxcolor = {0.05, 0.6, 0.6, 1.0}, + bartype = bitShowGlyph + bitUseOverlay + bitPercentage + bitModular + bitInverse + bitLeft + bitVertical, + hidethreshold = 0.99, + uniformindex = unitPrimaryReloadChannel, + uvoffset = VBAR_COL_GENERIC_RELOAD, layoutSlot = 0, -- overridden per-unit by weapon_class in addBarForUnit + }, + reload2 = { -- weapon 2: right, top + mincolor = {0.03, 0.4, 0.4, 1.0}, maxcolor = {0.05, 0.6, 0.6, 1.0}, + bartype = bitShowGlyph + bitUseOverlay + bitPercentage + bitModular + bitInverse + bitRight + bitVertical, + hidethreshold = 0.99, uniformindex = unitPrimaryCountChannel, uvoffset = VBAR_COL_GENERIC_RELOAD, layoutSlot = 0, + }, + reload3 = { -- weapon 3: left, second row + mincolor = {0.03, 0.4, 0.4, 1.0}, maxcolor = {0.05, 0.6, 0.6, 1.0}, + bartype = bitShowGlyph + bitUseOverlay + bitPercentage + bitModular + bitInverse + bitLeft + bitVertical, + hidethreshold = 0.99, uniformindex = unitSecondaryReloadChannel, uvoffset = VBAR_COL_GENERIC_RELOAD, layoutSlot = 1, + }, + reload4 = { -- weapon 4: right, second row + mincolor = {0.03, 0.4, 0.4, 1.0}, maxcolor = {0.05, 0.6, 0.6, 1.0}, + bartype = bitShowGlyph + bitUseOverlay + bitPercentage + bitModular + bitInverse + bitRight + bitVertical, + hidethreshold = 0.99, uniformindex = unitSecondaryCountChannel, uvoffset = VBAR_COL_GENERIC_RELOAD, layoutSlot = 1, + }, + primarycount = { + mincolor = {0.03, 0.4, 0.4, 1.0}, + maxcolor = {0.05, 0.6, 0.6, 1.0}, + bartype = bitShowGlyph + bitUseOverlay + bitIntegerNumber + bitLeft + bitVertical, + hidethreshold = 0.99, + uniformindex = unitPrimaryCountChannel, + uvoffset = VBAR_COL_GENERIC_RELOAD, -- overridden per-unit by weapon_class in addBarForUnit + }, + -- Individual burst reload bars (ch9-12, sorted most-loaded first). + -- Added dynamically up to burstCount; value 0.0 hides naturally via isVarForChannelVisible. + bustreload1 = { -- burst weapons are "related": all in the left column, stacked + mincolor = {0.03, 0.4, 0.4, 1.0}, maxcolor = {0.05, 0.6, 0.6, 1.0}, + bartype = bitPercentage + bitColorCorrect + bitLeft + bitVertical, + hidethreshold = 0.99, uniformindex = unitPrimaryReloadChannel, uvoffset = VBAR_COL_GENERIC_RELOAD, layoutSlot = 0, + }, + bustreload2 = { + mincolor = {0.03, 0.4, 0.4, 1.0}, maxcolor = {0.05, 0.6, 0.6, 1.0}, + bartype = bitPercentage + bitColorCorrect + bitLeft + bitVertical, + hidethreshold = 0.99, uniformindex = unitPrimaryCountChannel, uvoffset = VBAR_COL_GENERIC_RELOAD, layoutSlot = 1, + }, + bustreload3 = { + mincolor = {0.03, 0.4, 0.4, 1.0}, maxcolor = {0.05, 0.6, 0.6, 1.0}, + bartype = bitPercentage + bitColorCorrect + bitLeft + bitVertical, + hidethreshold = 0.99, uniformindex = unitSecondaryReloadChannel, uvoffset = VBAR_COL_GENERIC_RELOAD, layoutSlot = 2, + }, + bustreload4 = { + mincolor = {0.03, 0.4, 0.4, 1.0}, maxcolor = {0.05, 0.6, 0.6, 1.0}, + bartype = bitPercentage + bitColorCorrect + bitLeft + bitVertical, + hidethreshold = 0.99, uniformindex = unitSecondaryCountChannel, uvoffset = VBAR_COL_GENERIC_RELOAD, layoutSlot = 3, + }, + dgun = { + mincolor = {1.0, 1.0, 1.0, 1.0}, + maxcolor = {1.0, 1.0, 1.0, 1.0}, + bartype = bitModular + bitInverse + bitRight + bitVertical, + hidethreshold = 0.99, + uniformindex = unitSecondaryReloadChannel, + uvoffset = VBAR_COL_DGUN, + }, + -- Gauges (radial fill = current 0..1 level, not a countdown). maxcolor is the fill color. + teleport = { -- non-weapon ability -> below zone (no left/right). frame-based countdown to teleportend. + mincolor = {0.2, 0.8, 1.0, 1.0}, + maxcolor = {0.2, 0.8, 1.0, 1.0}, -- cyan + bartype = bitVertical + bitModular + bitInverse, -- radial timer; fills as it completes + hidethreshold = 0.99, + uniformindex = unitSecondaryReloadChannel, + uvoffset = 0, + statusIcon = "teleport", -- beacon symbol + }, + heat = { + mincolor = {1.0, 0.45, 0.1, 1.0}, + maxcolor = {1.0, 0.45, 0.1, 1.0}, -- orange (heat) + bartype = bitVertical + bitGauge + bitRight, + hidethreshold = 0.99, + uniformindex = unitSecondaryReloadChannel, + uvoffset = -1, -- one-off; no symbol (fill + color identify it) + }, + speed = { + mincolor = {1.0, 0.9, 0.2, 1.0}, + maxcolor = {1.0, 0.9, 0.2, 1.0}, -- yellow (superweapon charge warning) + bartype = bitVertical + bitGauge + bitRight, + hidethreshold = 0.99, + uniformindex = unitSecondaryReloadChannel, + uvoffset = -1, -- one-off; no symbol (fill + color identify it) + }, + reammo = { -- rearm -> below zone rate-ETA: counts down while rearming on a pad, grey static off-pad. + mincolor = {1.0, 0.6, 0.1, 1.0}, + maxcolor = {1.0, 0.6, 0.1, 1.0}, -- orange (ammo / forward-progress band color) + bartype = bitVertical + bitRateETA, + hidethreshold = 0.99, + uniformindex = unitPrimaryReloadChannel, + uvoffset = 0, + statusIcon = "rearm", + }, + goo = { -- Puppy goo -> below zone pausable ETA: smooth countdown to replication while reclaiming next + -- to metal, needle frozen (static) when stopped. Same badge look either way. + mincolor = {0.6, 0.9, 0.3, 1.0}, + maxcolor = {0.6, 0.9, 0.3, 1.0}, -- greenish (goo) + bartype = bitVertical + bitRateETA, + hidethreshold = 0.99, + uniformindex = unitGooChannel, + uvoffset = 0, + statusIcon = "goo", + }, + -- Jump charges -> below zone. One per charge (jump/jump2/jump3); all read the same slot (a + -- reconstructed jumpReload, 0..charges) and each subtracts its baked charge index, so charge N shows + -- full once jumpReload passes N. Per-instance: uvoffset = chargeIndex + charges*16, range = reload + -- frames, layoutSlot = packed (index, below-count). Separate barTypeMap names so removal iterates them. + jump = { -- movement ability -> below zone. gauge per jump charge. + mincolor = {0.4, 0.9, 0.5, 1.0}, + maxcolor = {0.4, 0.9, 0.5, 1.0}, -- green (movement/jump) + bartype = bitVertical + bitGauge + bitJumpCharge, + hidethreshold = 0.99, + uniformindex = unitMovementChannel, + uvoffset = 0, + }, + jump2 = { + mincolor = {0.4, 0.9, 0.5, 1.0}, + maxcolor = {0.4, 0.9, 0.5, 1.0}, + bartype = bitVertical + bitGauge + bitJumpCharge, + hidethreshold = 0.99, + uniformindex = unitMovementChannel, + uvoffset = 0, + }, + jump3 = { + mincolor = {0.4, 0.9, 0.5, 1.0}, + maxcolor = {0.4, 0.9, 0.5, 1.0}, + bartype = bitVertical + bitGauge + bitJumpCharge, + hidethreshold = 0.99, + uniformindex = unitMovementChannel, + uvoffset = 0, + }, + captureReload = { + mincolor = {0.0, 0.0, 0.0, 0.0}, + maxcolor = {0.0, 0.0, 0.0, 0.0}, + bartype = bitPercentage + bitModular + bitInverse + bitLeft + bitVertical, + hidethreshold = 0.99, + uniformindex = unitPrimaryReloadChannel, + uvoffset = VBAR_COL_CAPTURERELOAD, + }, + ability = { -- Swift sprint etc.: non-weapon movement ability -> below zone (like jump). gauge fills + -- as it recharges (specialReloadRemaining counts down, so bitInverse turns it into a charge level). + mincolor = {0.4, 0.9, 0.5, 1.0}, + maxcolor = {0.4, 0.9, 0.5, 1.0}, -- green (movement ability, matches jump) + bartype = bitVertical + bitModular + bitInverse, -- frame-based cooldown; fills as it recharges + hidethreshold = 0.99, + uniformindex = unitMovementChannel, + uvoffset = 0, + statusIcon = "ability", -- sprint symbol + }, + stockpile = { + -- rate-ETA to the next missile (frame-based, grey static when stalled). The ready count is the + -- stockpilecount glyph co-located on this badge (same right-column slot 0). + mincolor = {0.6, 0.8, 1.0, 1.0}, + maxcolor = {0.6, 0.8, 1.0, 1.0}, -- light blue (forward-progress band color) + bartype = bitVertical + bitRateETA + bitRight, + hidethreshold = 1.99, + uniformindex = unitSecondaryReloadChannel, + uvoffset = 0, + statusIcon = "stockpile", -- missile symbol + }, + stockpilecount = { + -- integer readout of ready stockpiled missiles (ch12), drawn centered on the stockpile gauge. + mincolor = {0.6, 0.8, 1.0, 1.0}, + maxcolor = {0.6, 0.8, 1.0, 1.0}, -- light blue + bartype = bitShowGlyph + bitUseOverlay + bitIntegerNumber + bitVertical + bitRight, + hidethreshold = 1.99, + uniformindex = unitSecondaryCountChannel, + uvoffset = 0, + layoutSlot = 0, -- same slot as the stockpile gauge so the number overlays it + }, + shield = { + maxcolor = {0.1, 0.1, 1.0, 1.0}, + mincolor = {1.0, 0.1, 0.1, 1.0}, + bartype = bitShowGlyph + bitUseOverlay + bitPercentage + bitInverse, + hidethreshold = 0.99, + uniformindex = unitShieldChannel, + uvoffset = 1, + fill = "shield", + }, + capture = { + mincolor = {0.6, 1.0, 0.7, 1.0}, + maxcolor = {0.6, 1.0, 0.7, 1.0}, + bartype = bitShowGlyph + bitUseOverlay + bitPercentage, + hidethreshold = 0.99, + uniformindex = unitCaptureChannel, + uvoffset = 0, + fill = "capture", + }, + featurehealth = { + mincolor = {0.25, 0.25, 0.25, 1.0}, + maxcolor = {0.65, 0.65, 0.65, 1.0}, + bartype = bitShowGlyph + bitPercentage, + hidethreshold = 0.99, + uniformindex = featureHealthChannel, + uvoffset = 18, + fill = "health", + }, + featurereclaim = { + -- white so the pre-colored fill art (reclaim.png) shows as authored (fill is color-multiplied) + mincolor = {1.0, 1.0, 1.0, 1.0}, + maxcolor = {1.0, 1.0, 1.0, 1.0}, + bartype = bitShowGlyph + bitPercentage, + hidethreshold = 0.99, + uniformindex = featureReclaimChannel, + uvoffset = 4, + fill = "reclaim", + }, + featureresurrect = { + -- Resurrect ("raise") progress fill bar (channel = resurrect progress 0..1, 0 when not raising). + -- White so the pre-colored fill art (resurrect.png) shows as authored. + mincolor = {1.0, 1.0, 1.0, 1.0}, + maxcolor = {1.0, 1.0, 1.0, 1.0}, + bartype = bitShowGlyph + bitPercentage, + hidethreshold = 0.99, + uniformindex = featureResurrectChannel, + uvoffset = 5, + fill = "resurrect", + }, + icon = { + mincolor = {1.0, 1.0, 1.0, 1.0}, + maxcolor = {1.0, 1.0, 1.0, 1.0}, + bartype = bitIcon, + hidethreshold = -1, + uniformindex = 0, + uvoffset = 0, + }, +} + +for barname, bt in pairs(barTypeMap) do + local cache = {} + for i=1,20 do cache[i] = 0 end + + --cache[1] = unitDefHeights[unitDefID] + additionalheightaboveunit * effectiveScale -- height + --cache[2] = sizeModifier + cache[3] = 1 -- range + cache[4] = tonumber(bt.uvoffset) -- glyph uv offset + + cache[5] = bt.bartype -- bartype int + --cache[6] = 0.0 -- unused + cache[7] = bt.uniformindex -- ssbo location offset (> 20 for health) + cache[8] = bt.layoutSlot or 0 -- layout slot within its zone (rides bartype_index_ssboloc.w) + + cache[9] = bt.mincolor[1] + cache[10] = bt.mincolor[2] + cache[11] = bt.mincolor[3] + cache[12] = bt.mincolor[4] + + cache[13] = bt.maxcolor[1] + cache[14] = bt.maxcolor[2] + cache[15] = bt.maxcolor[3] + cache[16] = bt.maxcolor[4] + + bt['cache'] = cache +end + +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- + + + +local spec, fullview = Spring.GetSpectatingState() +local myTeamID = Spring.GetMyTeamID() +local myAllyTeamID = Spring.GetMyAllyTeamID() +local myPlayerID = Spring.GetMyPlayerID() +local gameSpeed = Game.gameSpeed + +-- Count of persistent below-zone badges per unitDef (jump charges + sprint + teleport). Set in +-- addBarsForUnit; read when adding the transient morph badge so it centers within the same run. +local unitDefBelowCount = {} +local unitDefBelowMask = {} -- bitmask of the persistent below-badge channels, so the live morph badge can + -- count how many are currently VISIBLE (slot nonzero) and re-center the run + +local chobbyInterface + + +local featureDefHeights = {} -- maps FeatureDefs to height + + +local featureVBO + +local barScale = 1 -- Option 'healthbarsscale' +local variableBarSizes = true -- Option 'healthbarsvariable' + +--local resurrectableFeaturesFast = {} -- value is this is for keeping an eye on resurrectable features, maybe store resurrect progress here? +--local resurrectableFeaturesSlow = {} -- this is for keeping an eye on resurrectable features, maybe store resurrect progress here? +--local reclaimableFeaturesSlow = {} -- for faster updates of features being reclaimed/rezzed +--local reclaimableFeaturesFast = {} -- for faster updates of features being reclaimed/rezzed + +-------------------------------------------------------------------------------- +-- GL4 Backend stuff: +local healthBarVBO = nil +local healthBarShader = nil + +local luaShaderDir = "LuaUI/Widgets/Include/" +local LuaShader = VFS.Include(luaShaderDir.."LuaShader.lua") +VFS.Include(luaShaderDir.."instancevbotable.lua") + +-------------------- configurables ----------------------- +local additionalheightaboveunit = 24 --16? +local featureHealthDistMult = 7 -- how many times closer features have to be for their bars to show +local featureReclaimDistMult = 2 -- how many times closer features have to be for their bars to show +local featureResurrectDistMult = 1 -- how many times closer features have to be for their bars to show +local glphydistmult = 3.5 -- how much closer than BARFADEEND the bar has to be to start drawing numbers/icons. Numbers closer to 1 will make the glyphs be drawn earlier, high numbers will only shows glyphs when zoomed in hard. +local glyphdistmultfeatures = 1.8 -- how much closer than BARFADEEND the bar has to be to start drawing numbers/icons + +local unitDefSizeMultipliers = {} -- table of unitdefID to a size mult (default 1.0) to override sizing of bars per unitdef +local skipGlyphsNumbers = 0.0 -- 0.0 is draw glyph and number, 1.0 means only numbers, 2.0 means only bars, + +local debugmode = false + + +local barHeight = 0.9 +local shaderConfig = { -- these are our shader defines + HEIGHTOFFSET = 3, -- Additional height added to everything + CLIPTOLERANCE = 1.1, -- At 1.0 it wont draw at units just outside of view (may pop in), 1.1 is a good safe amount + MAXVERTICES = 64, -- The max number of vertices we can emit, make sure this is consistent with what you are trying to draw (tris 3, quads 4, corneredrect 8, circle 64 + CLIPTOLERANCE = 1.2, + BARWIDTH = 2.56, + BARHEIGHT = barHeight, + BGBOTTOMCOLOR = "vec4(0.25, 0.25, 0.25, 1.0)", + BGTOPCOLOR = "vec4(0.1, 0.1, 0.1, 1.0)", + BARSCALE = 4.0, + PERCENT_VISIBILITY_MAX = 0.99, + TIMER_VISIBILITY_MIN = 0.0, + BARSTEP = 10, -- pixels to downshift per new bar + BOTTOMDARKENFACTOR = 0.5, + + BARFADESTART = 3200, + BARFADEEND = 3800, + ATLASSTEPY = 0.03125, + ATLASSTEPX = 0.0625, + MINALPHA = 0.2, + ICONATLAS_COLS = 16, + ICONATLAS_ROWS = 32, + BARFILLCELLS = 9, -- a 576px horizontal fill bar spans this many 64px atlas cells + ICONCORNERSCALE = 0.5, -- corner badge (rank/group) size as a fraction of the unit icon's half-extent +} +shaderConfig.BARCORNER = 0.06 + (shaderConfig.BARHEIGHT / 9) +shaderConfig.SMALLERCORNER = shaderConfig.BARCORNER * 0.6 + +-- Bit-pack descriptor arrays for the shader's readField (generated from channelPack in gl_uniform_channels). +local packFloatInit, packOffsetInit, packWidthInit, packTypeInit = buildChannelPackDefines() +shaderConfig.PACK_FLOAT_INIT = packFloatInit +shaderConfig.PACK_OFFSET_INIT = packOffsetInit +shaderConfig.PACK_WIDTH_INIT = packWidthInit +shaderConfig.PACK_TYPE_INIT = packTypeInit + +if debugmode then + shaderConfig.DEBUGSHOW = 1 -- comment this to always show all bars +end + +local vsSrcPath = "LuaUI/Widgets/Shaders/UnitOverlayGL4.vert.glsl" +local gsSrcPath = "LuaUI/Widgets/Shaders/UnitOverlayGL4.geom.glsl" +local fsSrcPath = "LuaUI/Widgets/Shaders/UnitOverlayGL4.frag.glsl" + +local shaderSourceCache = { + vssrcpath = vsSrcPath, + fssrcpath = fsSrcPath, + gssrcpath = gsSrcPath, + shaderName = "Health Bars Shader GL4", + uniformInt = { + iconAtlasTex = 1, + }, + uniformFloat = { + --addRadius = 1, + jumpIconCell = 0, + iconDistance = 27, + cameraDistanceMult = 1.0, + cameraDistanceMultGlyph = 4.0, + skipGlyphsNumbers = 0.0, + overallScale = 1.0, + barSize = 1.0, + vbarUserX = 0.0, + vbarSize = 1.0, + iconSize = 1.0, + barBorderWidth = 0.25, + trackDarken = 0.25, + reloadThreshold = 2.0, + pulseAlpha = 1.0, + rowOffset = 0.0, + rowSize = 1.0, + rowSpacing = 1.0, + isFeature = 0.0, + barOffset = 0.0, + barSpacing = 1.0, + belowBadgeHeight = 0.0, + overlayDepthBand = 0.0, + }, + shaderConfig = shaderConfig, + } + +-- Walk through unitdefs for the stuff we need: +for udefID, unitDef in pairs(UnitDefs) do + -- BAR PLACEMENT + unitDefHeights[udefID] = unitDef.height + unitDefSizeMultipliers[udefID] = math.min(1.45, math.max(0.85, (Spring.GetUnitDefDimensions(udefID).radius / 150) + math.min(0.6, unitDef.power / 4000))) + math.min(0.6, unitDef.health / 22000) +end + +for fdefID, featureDef in pairs(FeatureDefs) do + --Spring.Echo(featureDef.name, featureDef.height) + featureDefHeights[fdefID] = featureDef.height or 32 +end + +local function goodbye(reason) + Spring.Echo("Unit Overlay GL4 widget exiting with reason: "..reason) + widgetHandler:RemoveWidget() +end + +local function initializeInstanceVBOTable(myName, usesFeatures) + local newVBOTable + newVBOTable = makeInstanceVBOTable( + { + {id = 0, name = 'height_timers', size = 4}, + {id = 1, name = 'type_index_ssboloc', size = 4, type = GL.UNSIGNED_INT}, + {id = 2, name = 'startcolor', size = 4}, + {id = 3, name = 'endcolor', size = 4}, + {id = 4, name = 'instData', size = 4, type = GL.UNSIGNED_INT}, + }, + 256, -- maxelements + myName, -- name + 4 -- unitIDattribID (instData) + ) + if newVBOTable == nil then goodbye("Failed to create " .. myName) end + + local newVAO = gl.GetVAO() + newVAO:AttachVertexBuffer(newVBOTable.instanceVBO) + newVBOTable.VAO = newVAO + if usesFeatures then newVBOTable.featureIDs = true end + return newVBOTable +end + +local iconAtlasIndexToPath = {} +local iconAtlasNextIndex = 0 +local iconAtlasReady = false +-- gl.Texture() can report a texture as bound (and TextureInfo gives its size) before its pixels are +-- actually uploaded to the GPU, so the first blit copies a BLANK cell yet we'd mark the atlas ready. +-- Keep re-blitting for a short settle window so those late uploads get captured. (Diagnosed via the +-- debug logging: every attempt showed allLoaded=true / bound=true / xsize=64 yet the cell was blank.) +local ICON_ATLAS_SETTLE_FRAMES = 15 +local iconAtlasSettle = 0 +local dbgAtlasAttempt = 0 -- DEBUG (remove later): renderIconAtlas attempts since the last build + +-- Dynamic atlas insertion for arbitrary WG.icons textures (states/ranks/gadget/build icons). The +-- atlas is an FBO, so new textures are blitted into free cells at runtime (flushed in DrawWorld). +local dynamicIconIndex = {} -- texture path -> atlas cell +local pendingDynamicIcons = {} -- {path, cell} awaiting a blit into the atlas FBO +local function registerDynamicIcon(path) + if not path then return nil end + if dynamicIconIndex[path] then return dynamicIconIndex[path] end + if iconAtlasNextIndex >= iconAtlasCols * iconAtlasRows then + Spring.Echo("Unit Overlay GL4: icon atlas full, cannot add", path) + return nil + end + local cell = iconAtlasNextIndex + iconAtlasNextIndex = iconAtlasNextIndex + 1 + dynamicIconIndex[path] = cell + pendingDynamicIcons[#pendingDynamicIcons + 1] = { path = path, cell = cell } + return cell +end + +-- Blit any newly-registered icon textures into the existing atlas FBO. Must run in a GL context +-- (called from DrawWorld). FBO content persists, so this appends without disturbing prior cells. +local function flushPendingIcons() + if not iconAtlasReady or #pendingDynamicIcons == 0 then return end + local stillPending = {} -- icons whose texture hasn't finished loading get retried next frame + gl.RenderToTexture(iconAtlasTexture, function() + -- Explicit state: the blit must be an exact copy regardless of whatever GL state the previous + -- (possibly just-reloaded) widget left behind -- a stale gl.Color or blend mode otherwise makes + -- the atlas come out modulated/blank, which alternates across /luaui reloads. + gl.DepthTest(false) + gl.Blending(false) + gl.Color(1, 1, 1, 1) + for i = 1, #pendingDynamicIcons do + local e = pendingDynamicIcons[i] + if VFS.FileExists(e.path) then + local col = e.cell % iconAtlasCols + local row = math.floor(e.cell / iconAtlasCols) + if gl.Texture(e.path) then + gl.TexRect((col / iconAtlasCols) * 2 - 1, (row / iconAtlasRows) * 2 - 1, + ((col + 1) / iconAtlasCols) * 2 - 1, ((row + 1) / iconAtlasRows) * 2 - 1) + else + stillPending[#stillPending + 1] = e -- not loaded yet + end + gl.Texture(false) + end + end + end) + pendingDynamicIcons = stillPending +end + +-------------------------------------------------------------------------------- +-- WG.icons backend: the overlay renders all hovering icons (states, ranks, gadget, build commands) +-- as instanced billboards in a centered row above each unit, replacing the legacy unit_icons.lua. +-- Provider widgets call WG.icons.SetUnitIcon/etc. unchanged. State changes mark a unit dirty; +-- the actual instance push/pop + atlas blits happen in DrawWorld (GL + VBO ready). +-------------------------------------------------------------------------------- +-- We hand the per-unit count of visible state icons to the shader so it can center the states and the +-- GPU-counted status badges as one combined row (channel 15 = userDefined[3][3]). The uniform updater +-- owns the GL write (sole writer of the unit buffer); we push the count via WG.SetUnitStateCount. +local function writeStateCount(unitID, count) + if WG.SetUnitStateCount then + WG.SetUnitStateCount(unitID, count) + end +end + +-- Providers register their icons once (often only on a state *change*) and cache that they did, so +-- they won't re-send when THIS widget reloads. The registry is therefore persisted on WG and restored +-- here; per-unit entries keep the texture path so cells can be re-resolved against the rebuilt atlas. +local iconState = WG.unitOverlayIconState or {} +WG.unitOverlayIconState = iconState +iconState.order = iconState.order or {} -- iconName -> order (lower = leftmost) +iconState.hidden = iconState.hidden or {} -- iconName -> true (globally hidden category) +iconState.pulse = iconState.pulse or {} -- iconName -> true (flashing) +iconState.units = iconState.units or {} -- unitID -> { iconName -> {path, color} } + +local wgIconOrder = iconState.order +local wgIconHidden = iconState.hidden +local wgIconPulse = iconState.pulse +local wgUnitIcons = iconState.units +local wgIconOrderList = {} -- iconNames sorted by order (rebuilt from wgIconOrder below) +local wgUnitPushedNames = {} -- unitID -> { iconName -> true } currently in the VBO +local wgDirtyUnits = {} -- unitID -> true, needs relayout in DrawWorld +local wgUnitGroup = {} -- unitID -> control-group digit (0-9), drawn as a bottom-right corner badge +local wgUnitCommand = {} -- unitID -> current-command atlas cell, drawn as a bottom-left corner badge +local groupCornerColor = {0.7, 1.0, 0.7, 1.0} -- tint for the group-number digit (matches the old gl.Text) + +for n in pairs(wgIconOrder) do wgIconOrderList[#wgIconOrderList + 1] = n end +table.sort(wgIconOrderList, function(a, b) return wgIconOrder[a] < wgIconOrder[b] end) + +local function wgReorder(name, order) + wgIconOrder[name] = order + wgIconOrderList = {} + for n in pairs(wgIconOrder) do wgIconOrderList[#wgIconOrderList + 1] = n end + table.sort(wgIconOrderList, function(a, b) return wgIconOrder[a] < wgIconOrder[b] end) +end + +local function wgNewIconCache(cell, slot, rowHeight, color, pulse, sizeMod) + local c = {} + for i = 1, 20 do c[i] = 0 end + c[1] = rowHeight -- per-instance height (vert raises centerpos.y by this) + c[2] = sizeMod -- sizeModifier: matches the bars' effectiveScale so the row tracks the bar stack + c[3] = slot -- raw 0-based state index (rides v_range; the shader centers it across the row) + c[4] = cell -- atlas cell (UVOFFSET) + c[5] = bitIcon + bitIconRow + (pulse and bitPulse or 0) -- bartype + local r, g, b, a = 1, 1, 1, 1 + if color then r, g, b, a = color[1], color[2], color[3], color[4] or 1 end + c[9], c[10], c[11], c[12] = r, g, b, a + c[13], c[14], c[15], c[16] = r, g, b, a + return c +end + +-- A badge pinned to a corner of the unit icon (cornerSlot: 0 = top-left, 1 = bottom-right). Shares the +-- icon's baseline height + scale so it tracks the unit icon; the geom (BITICONCORNER) does the offset. +-- The center unit icon, with the rank badge (top-left) and group number (bottom-right) composited onto +-- the SAME quad by the shader -- one primitive at one depth, so nothing z-fights or sorts between them. +-- rankCell/groupCell are atlas cells (nil = absent); the shader reads rank from v_range and group from +-- bartype_index.w. teamColor tints the icon (mincolor); rankColor tints the rank badge (maxcolor); the +-- group number is tinted green by the FS. +local function wgNewClusterIconCache(iconCell, rankCell, groupCell, cmdCell, rowHeight, teamColor, rankColor, sizeMod) + local c = {} + for i = 1, 20 do c[i] = 0 end + c[1] = rowHeight + c[2] = sizeMod + c[3] = rankCell or -1 -- v_range -> rank atlas cell (-1 = no rank) + c[4] = iconCell or 0 -- uvOffset -> icon atlas cell + c[5] = bitIcon -- center unit icon (FS composites rank+group+command) + c[7] = cmdCell or 65535 -- bartype_index.z -> current-command atlas cell (>=60000 = none) + c[8] = groupCell or 65535 -- bartype_index.w -> group atlas cell (>=60000 = no group) + local r, g, b, a = 1, 1, 1, 1 + if teamColor then r, g, b, a = teamColor[1], teamColor[2], teamColor[3], teamColor[4] or 1 end + c[9], c[10], c[11], c[12] = r, g, b, a -- mincolor = team color (icon tint) + local rr, rg, rb = 1, 1, 1 + if rankColor then rr, rg, rb = rankColor[1], rankColor[2], rankColor[3] end + c[13], c[14], c[15], c[16] = rr, rg, rb, 1 -- maxcolor = rank tint + return c +end + +-- Pop a unit's icon instances and re-push the currently-visible ones, recentered. Runs in DrawWorld. +local function relayoutUnitIcons(unitID) + local pushed = wgUnitPushedNames[unitID] + if pushed then + for name in pairs(pushed) do + local key = unitID .. "_wgicon_" .. name + if healthBarVBO.instanceIDtoIndex[key] then popElementInstance(healthBarVBO, key) end + end + wgUnitPushedNames[unitID] = nil + end + if not Spring.ValidUnitID(unitID) then + wgUnitIcons[unitID] = nil -- unit is really gone; drop stale state + wgUnitGroup[unitID] = nil + wgUnitCommand[unitID] = nil + return + end + local unitDefID = Spring.GetUnitDefID(unitID) + local cp = UnitDefs[unitDefID or -1] + local extra = (cp and cp.customParams and tonumber(cp.customParams.health_bar_height)) or 0 + -- Same centred baseline as the bars; the row rides above the top bar, the icon sits at the centre. + local rowHeight = (Spring.GetUnitHeight(unitID) or unitDefHeights[unitDefID] or 0) * 0.5 + extra + local effectiveScale = ((variableBarSizes and unitDefSizeMultipliers[unitDefID]) or 1.0) * barScale + + local icons = wgUnitIcons[unitID] + local group = wgUnitGroup[unitID] + local rankData = icons and icons['rank'] + local rankVisible = rankData and not wgIconHidden['rank'] + local groupVisible = group and not wgIconHidden['group'] + + local pushedNames = {} + + -- ONE quad: the centre unit icon, with the rank badge (top-left) and group number (bottom-right) + -- composited onto it by the shader, so all three share a single depth (no z-fight, nothing sorts + -- between them). rank/group are optional atlas cells passed on the icon instance. + local rankCell + if rankVisible then + rankCell = registerDynamicIcon(rankData.path) or rankData.cell + rankData.cell = rankCell + end + local groupCell = groupVisible and digitAtlasIndex[group] or nil + -- current command (bottom-left corner): cell maintained by the throttled command poll; gated by the + -- 'command' visibility option (Always/Shift/Never) via wgIconHidden, like rank/group. + local commandCell = (not wgIconHidden['command']) and wgUnitCommand[unitID] or nil + local teamID = Spring.GetUnitTeam(unitID) + local tr, tg, tb, ta = Spring.GetTeamColor(teamID) + pushElementInstance(healthBarVBO, + wgNewClusterIconCache(unitDefIconIndex[unitDefID] or 0, rankCell, groupCell, commandCell, rowHeight, + {tr or 1, tg or 1, tb or 1, ta or 1}, rankData and rankData.color, effectiveScale), + unitID .. "_wgicon_icon", true, nil, unitID) + pushedNames['icon'] = true + + -- State-icon row (excludes rank/group, which are now corners of the icon above). + local visible = {} + if icons then + for i = 1, #wgIconOrderList do + local name = wgIconOrderList[i] + if name ~= 'rank' and icons[name] and not wgIconHidden[name] then + visible[#visible + 1] = name + end + end + end + local count = #visible + writeStateCount(unitID, count) -- the shader centers states + status badges around this count + for i = 1, count do + local name = visible[i] + local data = icons[name] + -- Resolve the cell from the texture path: a persisted entry that survived a reload still + -- carries the path, but its cached cell points into the old (pre-rebuild) atlas. + local cell = registerDynamicIcon(data.path) or data.cell + data.cell = cell + pushElementInstance(healthBarVBO, + wgNewIconCache(cell, i - 1, rowHeight, data.color, wgIconPulse[name], effectiveScale), + unitID .. "_wgicon_" .. name, true, nil, unitID) + pushedNames[name] = true + end + wgUnitPushedNames[unitID] = pushedNames +end + +local function processDirtyIcons() + if not iconAtlasReady or not next(wgDirtyUnits) then return end + for unitID in pairs(wgDirtyUnits) do + relayoutUnitIcons(unitID) + end + wgDirtyUnits = {} +end + +-- Control-group numbers (bottom-right corner badge). The overlay is the single renderer; it polls the +-- engine's control groups and diffs membership so units that join/leave a group are relaid out. +local lastPolledGroup = {} -- unitID -> group last seen by the poll (for clear-on-leave) +local function setUnitGroupNumber(unitID, group) + if wgUnitGroup[unitID] == group then return end + wgUnitGroup[unitID] = group + wgDirtyUnits[unitID] = true +end + +local function refreshGroupNumbers() + local current = {} + if not wgIconHidden['group'] then + for groupID in pairs(Spring.GetGroupList() or {}) do + local units = Spring.GetGroupUnits(groupID) or {} + for i = 1, #units do current[units[i]] = groupID end + end + end + for unitID in pairs(lastPolledGroup) do + if current[unitID] == nil then setUnitGroupNumber(unitID, nil) end + end + for unitID, groupID in pairs(current) do + setUnitGroupNumber(unitID, groupID) + end + lastPolledGroup = current +end + +-- Current-command corner (bottom-left). Maps the unit's active command to a Bold/ command icon, polled on +-- a throttle and diffed like the group numbers. registerDynamicIcon is idempotent (returns the cached +-- cell), so re-polling the same command is cheap, and only changed units get re-pushed. +local SUCMD = Spring.Utilities and Spring.Utilities.CMD +local commandIconPath = { + [CMD.MOVE] = "LuaUI/Images/commands/Bold/move.png", + [CMD.ATTACK] = "LuaUI/Images/commands/Bold/attack.png", + [CMD.FIGHT] = "LuaUI/Images/commands/Bold/fight.png", + [CMD.PATROL] = "LuaUI/Images/commands/Bold/patrol.png", + [CMD.GUARD] = "LuaUI/Images/commands/Bold/guard.png", + [CMD.REPAIR] = "LuaUI/Images/commands/Bold/repair.png", + [CMD.RECLAIM] = "LuaUI/Images/commands/Bold/reclaim.png", + [CMD.RESURRECT] = "LuaUI/Images/commands/Bold/resurrect.png", + [CMD.CAPTURE] = "LuaUI/Images/commands/Bold/capture.png", + [CMD.LOAD_UNITS] = "LuaUI/Images/commands/Bold/load.png", + [CMD.UNLOAD_UNITS] = "LuaUI/Images/commands/Bold/unload.png", + [CMD.MANUALFIRE] = "LuaUI/Images/commands/Bold/dgun.png", + [CMD.WAIT] = "LuaUI/Images/commands/Bold/wait.png", +} +if SUCMD then + if SUCMD.RAW_MOVE then commandIconPath[SUCMD.RAW_MOVE] = "LuaUI/Images/commands/Bold/move.png" end + if SUCMD.RAW_BUILD then commandIconPath[SUCMD.RAW_BUILD] = "LuaUI/Images/commands/Bold/build.png" end + if SUCMD.JUMP then commandIconPath[SUCMD.JUMP] = "LuaUI/Images/commands/Bold/jump.png" end +end + +local function commandCellFor(unitID) + local cmdID = Spring.GetUnitCurrentCommand(unitID) + if not cmdID then return nil end -- idle, no command + local path = (cmdID < 0) and "LuaUI/Images/commands/Bold/build.png" or commandIconPath[cmdID] + return path and registerDynamicIcon(path) or nil +end + +local function refreshUnitCommands() + if wgIconHidden['command'] then return end -- display off -> skip the poll entirely + for unitID in pairs(wgUnitPushedNames) do + local cell = commandCellFor(unitID) + if wgUnitCommand[unitID] ~= cell then + wgUnitCommand[unitID] = cell + wgDirtyUnits[unitID] = true + end + end +end + +WG.icons = {} + +function WG.icons.SetUnitIcon(unitID, data) + local name = data.name + if not name then return end + if not wgIconOrder[name] then wgReorder(name, math.huge) end + local icons = wgUnitIcons[unitID] + if data.texture then + local cell = registerDynamicIcon(data.texture) + if not cell then return end + if not icons then icons = {}; wgUnitIcons[unitID] = icons end + icons[name] = { cell = cell, color = data.color, path = data.texture } + elseif icons then + icons[name] = nil + end + wgDirtyUnits[unitID] = true +end + +function WG.icons.SetDisplay(name, show) + local hide = (not show) or nil + if wgIconHidden[name] ~= hide then + wgIconHidden[name] = hide + for unitID in pairs(wgUnitIcons) do wgDirtyUnits[unitID] = true end + end +end + +function WG.icons.SetOrder(name, order) + wgReorder(name, order) + for unitID in pairs(wgUnitIcons) do wgDirtyUnits[unitID] = true end +end + +function WG.icons.SetPulse(name, pulse) + wgIconPulse[name] = pulse or nil + for unitID, icons in pairs(wgUnitIcons) do + if icons[name] then wgDirtyUnits[unitID] = true end + end +end + +-- State-icon visibility control (single source of truth). Driven by the 'Unit States' options above: +-- bool options map straight to show/hide; tri-state options resolve Always/Never directly and 'shift' +-- against whether Shift is currently held (tracked via KeyPress/KeyRelease -> refreshShift). +local stateShiftHeld = false +function stateCtl.apply(name) + local opt = options['state_' .. name] + if not opt then return end + local show + if opt.type == 'bool' then + show = opt.value and true or false + elseif opt.value == 'always' then + show = true + elseif opt.value == 'never' then + show = false + else -- 'shift' + show = stateShiftHeld + end + WG.icons.SetDisplay(name, show) + -- group numbers aren't pushed through WG.icons (they're polled), so re-evaluate membership now + -- that the hidden flag changed (adds badges when enabled, clears them when disabled). + if name == 'group' then refreshGroupNumbers() end + -- the current-command corner is also polled, not pushed through WG.icons. Re-dirty every drawn icon + -- so the corner appears/disappears immediately, then (re)poll to fill the cells. + if name == 'command' then + for u in pairs(wgUnitPushedNames) do wgDirtyUnits[u] = true end + refreshUnitCommands() + end +end + +function stateCtl.applyAll() + for _, s in ipairs(stateIconBool) do stateCtl.apply(s.name) end + for _, s in ipairs(stateIconTri) do stateCtl.apply(s.name) end +end + +-- Re-evaluate Shift-gated states when the Shift key state changes. +function stateCtl.refreshShift() + local _, _, _, shift = Spring.GetModKeyState() + shift = shift and true or false + if shift ~= stateShiftHeld then + stateShiftHeld = shift + for _, s in ipairs(stateIconTri) do + if options['state_' .. s.name].value == 'shift' then stateCtl.apply(s.name) end + end + end +end + +-- Called from UnitDestroyed (which UnitFinished also reuses). Pop the unit's icon instances and mark +-- it dirty: relayout next frame re-pushes them if the unit is still alive (UnitFinished case) or +-- clears the stale state if it's really gone (relayout's ValidUnitID check). +local function onUnitIconHolderReset(unitID) + local pushed = wgUnitPushedNames[unitID] + if pushed and healthBarVBO then + for name in pairs(pushed) do + local key = unitID .. "_wgicon_" .. name + if healthBarVBO.instanceIDtoIndex[key] then popElementInstance(healthBarVBO, key) end + end + wgUnitPushedNames[unitID] = nil + end + if wgUnitIcons[unitID] then wgDirtyUnits[unitID] = true end +end + +-- After the healthBarVBO is wiped (init / VisibleUnitsChanged), our icon instances are gone too; +-- forget them and re-push from the preserved state next frame. +local function requeueAllIcons() + wgUnitPushedNames = {} + for unitID in pairs(wgUnitIcons) do wgDirtyUnits[unitID] = true end + for unitID in pairs(wgUnitGroup) do wgDirtyUnits[unitID] = true end -- group-only units have no wgUnitIcons entry +end + +local function buildIconAtlas() + local iconTypeToIndex = {} + + for udefID, unitDef in pairs(UnitDefs) do + local iconType = unitDef.iconType or "default" + if not iconTypeToIndex[iconType] then + local iconDef = icontypes[iconType] + local texPath + if iconDef and iconDef.bitmap then + texPath = iconDef.bitmap + else + texPath = 'icons/' .. iconType .. iconFormat + if not VFS.FileExists(texPath) then + texPath = 'icons/default' .. iconFormat + end + end + iconTypeToIndex[iconType] = iconAtlasNextIndex + iconAtlasIndexToPath[iconAtlasNextIndex] = texPath + iconAtlasNextIndex = iconAtlasNextIndex + 1 + end + unitDefIconIndex[udefID] = iconTypeToIndex[iconType] + end + + -- (Reload-badge icons are no longer pre-registered here: each weapon's `icon` customParam path is + -- registered on demand via registerDynamicIcon when the reload bar is added.) + + -- Status-duration badge icons (one cell each) from the Bold command icon set. + for status, img in pairs(statusIconImage) do + local path = "LuaUI/Images/commands/Bold/" .. img .. ".png" + if VFS.FileExists(path) then + statusIconIndex[status] = iconAtlasNextIndex + iconAtlasIndexToPath[iconAtlasNextIndex] = path + iconAtlasNextIndex = iconAtlasNextIndex + 1 + end + end + + -- Jump-charge gauge badge icon (one cell): composited into the jump gauges by the shader. + if VFS.FileExists(jumpIconPath) then + jumpIconAtlasIndex = iconAtlasNextIndex + iconAtlasIndexToPath[iconAtlasNextIndex] = jumpIconPath + iconAtlasNextIndex = iconAtlasNextIndex + 1 + end + + -- Reserve a run of `count` contiguous cells for a wide image (digit strip / fill bar), kept + -- within a single atlas row so renderIconAtlas can blit it in one piece -- a run that wrapped + -- past the row edge would split across rows. Pads to the next row start if it wouldn't fit in + -- the current row's remainder. Returns the starting cell index, or nil if the file is missing. + local function addStripToAtlas(path, count) + if not VFS.FileExists(path) then return nil end + if (iconAtlasNextIndex % iconAtlasCols) + count > iconAtlasCols then + iconAtlasNextIndex = (math.floor(iconAtlasNextIndex / iconAtlasCols) + 1) * iconAtlasCols + end + local startIndex = iconAtlasNextIndex + iconAtlasIndexToPath[startIndex] = { strip = path, count = count } + iconAtlasNextIndex = iconAtlasNextIndex + count + return startIndex + end + + -- Digit strip: 12 glyphs in one row; record where each digit/symbol landed. + local digitStart = addStripToAtlas(digitStripPath, #digitStripGlyphs) + if digitStart then + digitAtlasStartIndex = digitStart + for i, glyph in ipairs(digitStripGlyphs) do + digitAtlasIndex[glyph] = digitStart + (i - 1) + end + end + + -- Horizontal fill bars: each 576x64 fill occupies barFillCells (9) contiguous cells. + for _, name in ipairs(barAtlasFills) do + local idx = addStripToAtlas("LuaUI/Images/bars/" .. name .. ".png", barFillCells) + if idx then barAtlasIndex[name] = idx end + end + + local atlasW = iconAtlasCols * iconAtlasCellSize + local atlasH = iconAtlasRows * iconAtlasCellSize + iconAtlasTexture = gl.CreateTexture(atlasW, atlasH, { + min_filter = GL.LINEAR, + mag_filter = GL.LINEAR, + wrap_s = GL.CLAMP_TO_EDGE, + wrap_t = GL.CLAMP_TO_EDGE, + fbo = true, + }) + -- Shared so other widgets (e.g. gui_bars_screen) can render bars/glyphs from the same atlas. + WG.UnitOverlayIconAtlas = iconAtlasTexture + iconAtlasReady = false -- a freshly created (blank) texture must be (re)blitted by renderIconAtlas + iconAtlasSettle = 0 + dbgAtlasAttempt = 0 -- DEBUG (remove later) +end + +local function renderIconAtlas() + if iconAtlasReady or not iconAtlasTexture then return end + local allLoaded = true -- a texture not yet loaded this frame leaves a blank cell -> retry next frame + + -- DEBUG (remove later): track which cells fail and watch the Detriment's icon cell specifically. + dbgAtlasAttempt = dbgAtlasAttempt + 1 + local dbgNotBound, dbgNotDecoded, dbgMissing = {}, {}, {} + local dd = UnitDefNames and UnitDefNames["striderdetriment"] + local dbgDetCell = dd and unitDefIconIndex[dd.id] + local dbgDetLine = nil + -- /DEBUG + + gl.RenderToTexture(iconAtlasTexture, function() + -- Clear only on the first blit; settle re-blits overwrite each base cell in place (no clear), + -- so dynamic WG.icons appended by flushPendingIcons between frames aren't wiped. + if iconAtlasSettle == 0 then gl.Clear(GL.COLOR_BUFFER_BIT, 0, 0, 0, 0) end + -- Explicit state so the blit is an exact copy independent of inherited GL state (see + -- flushPendingIcons): a stale gl.Color/blend left by another reloaded widget otherwise makes + -- the atlas blank on alternating /luaui reloads. + gl.DepthTest(false) + gl.Blending(false) + gl.Color(1, 1, 1, 1) + for idx = 0, iconAtlasNextIndex - 1 do + local entry = iconAtlasIndexToPath[idx] + if entry then + local col = idx % iconAtlasCols + local row = math.floor(idx / iconAtlasCols) + local y1 = (row / iconAtlasRows) * 2 - 1 + local y2 = ((row + 1) / iconAtlasRows) * 2 - 1 + if type(entry) == "table" then + -- Multi-cell glyph strip: blit the whole image across `count` contiguous cells + -- in this row, so each 64px glyph aligns exactly to one cell boundary. + if VFS.FileExists(entry.strip) then + local x1 = (col / iconAtlasCols) * 2 - 1 + local x2 = ((col + entry.count) / iconAtlasCols) * 2 - 1 + if gl.Texture(entry.strip) then + gl.TexRect(x1, y1, x2, y2) + else + allLoaded = false + dbgNotBound[#dbgNotBound + 1] = idx .. ":" .. entry.strip -- DEBUG + end + gl.Texture(false) + else + dbgMissing[#dbgMissing + 1] = idx .. ":" .. tostring(entry.strip) -- DEBUG + end + elseif VFS.FileExists(entry) then + local x1 = (col / iconAtlasCols) * 2 - 1 + local x2 = ((col + 1) / iconAtlasCols) * 2 - 1 + local bound = gl.Texture(entry) -- DEBUG: capture bind result + if bound then + gl.TexRect(x1, y1, x2, y2) + else + allLoaded = false + dbgNotBound[#dbgNotBound + 1] = idx .. ":" .. entry -- DEBUG + end + gl.Texture(false) + -- DEBUG: a bound-but-undecoded texture reports 0 size and blits blank + local ti = bound and gl.TextureInfo(entry) + local xs = ti and (ti.xsize or 0) or 0 + if bound and xs == 0 then dbgNotDecoded[#dbgNotDecoded + 1] = idx .. ":" .. entry end + if idx == dbgDetCell then + dbgDetLine = string.format("DETRIMENT cell=%d path=%s exists=true bound=%s xsize=%s", + idx, entry, tostring(bound), tostring(xs)) + end + else + dbgMissing[#dbgMissing + 1] = idx .. ":" .. tostring(entry) -- DEBUG + if idx == dbgDetCell then + dbgDetLine = string.format("DETRIMENT cell=%d path=%s exists=FALSE", idx, tostring(entry)) + end + end + elseif idx == dbgDetCell then + dbgDetLine = string.format("DETRIMENT cell=%d has NO entry (unitDefIconIndex points to empty cell)", idx) + end + end + end) + + -- DEBUG (remove later): summary once per build (first attempt) + any failures. + if dbgAtlasAttempt == 1 then + Spring.Echo(string.format("[OverlayAtlas DBG] attempt=%d allLoaded=%s cells=%d notBound=%d notDecoded=%d missing=%d detCell=%s settleFrames=%d", + dbgAtlasAttempt, tostring(allLoaded), iconAtlasNextIndex, #dbgNotBound, #dbgNotDecoded, #dbgMissing, tostring(dbgDetCell), ICON_ATLAS_SETTLE_FRAMES)) + if dbgDetLine then Spring.Echo("[OverlayAtlas DBG] " .. dbgDetLine) end + end + if #dbgNotBound > 0 then Spring.Echo("[OverlayAtlas DBG] NOT BOUND: " .. table.concat(dbgNotBound, ", ")) end + if #dbgNotDecoded > 0 then Spring.Echo("[OverlayAtlas DBG] BOUND BUT 0-SIZE: " .. table.concat(dbgNotDecoded, ", ")) end + if #dbgMissing > 0 then Spring.Echo("[OverlayAtlas DBG] MISSING FILE: " .. table.concat(dbgMissing, ", ")) end + -- /DEBUG + + -- Every source texture bound -- but a just-bound texture's pixels may not be uploaded yet, so keep + -- re-blitting for ICON_ATLAS_SETTLE_FRAMES to capture late uploads before latching ready. A texture + -- that fails to bind resets the settle so we keep retrying from scratch. + if allLoaded then + iconAtlasSettle = iconAtlasSettle + 1 + if iconAtlasSettle >= ICON_ATLAS_SETTLE_FRAMES then + iconAtlasReady = true + local capacity = iconAtlasCols * iconAtlasRows + Spring.Echo("Unit Overlay GL4: built icon atlas using", iconAtlasNextIndex, "of", capacity, "cells") + Spring.Echo(string.format("[OverlayAtlas DBG] READY after %d attempt(s)", dbgAtlasAttempt)) -- DEBUG + if iconAtlasNextIndex > capacity then + Spring.Echo("Unit Overlay GL4: WARNING icon atlas overflow -- increase iconAtlasRows / ICONATLAS_ROWS") + end + end + else + iconAtlasSettle = 0 + end +end + +local function initGL4() + healthBarShader = LuaShader.CheckShaderUpdates(shaderSourceCache) + + if not healthBarShader then goodbye("Failed to compile Unit Overlay GL4") end + + healthBarVBO = initializeInstanceVBOTable("healthBarVBO", false) + featureVBO = initializeInstanceVBOTable("featureVBO", true) + buildIconAtlas() + + -- Point each horizontal bar's fill at its atlas cell. cache[4]/UVOFFSET for horizontal bars + -- now means "atlas start cell of the 9-cell fill" instead of a healthbars.png patch index. + -- A horizontal bar with no fill art gets -1, which the shader renders as flat color. + -- Vertical (radial badge) and icon bars set cache[4] themselves (per-unit weapon/unit icon). + for _, bt in pairs(barTypeMap) do + local isVertical = bt.bartype % (bitVertical * 2) >= bitVertical + local isIcon = bt.bartype % (bitIcon * 2) >= bitIcon + if bt.statusIcon then + -- status-duration radial badge: cache[4]/UVOFFSET is the badge's atlas icon cell + bt.cache[4] = statusIconIndex[bt.statusIcon] or 0 + elseif not isVertical and not isIcon then + bt.cache[4] = (bt.fill and barAtlasIndex[bt.fill]) or -1 + end + end + + if debugmode then + healthBarVBO.debug = true + end +end + +-------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- + +local uniformcache = {0.0} + +-- uvOffsetOverride / layoutSlotOverride / alwaysShow let one barTypeMap config back several per-instance +-- variants (jump charges: same config, different charge index / centered slot / always-show flag). +local function addBarForUnit(unitID, unitDefID, barname, reason, range, uniformOverride, uvOffsetOverride, layoutSlotOverride, alwaysShow) + unitDefID = unitDefID or Spring.GetUnitDefID(unitID) + + -- Why? Because adding additional bars can be triggered from outside of unit tracker api + -- like EMP, where we assume that unit is already visible, however + -- debug units are not present in unittracker api! + if (unitDefID == nil) or unitDefIgnore[unitDefID] then return nil end + + local gf = Spring.GetGameFrame() + local bt = barTypeMap[barname] + if bt == nil then Spring.Echo(barname) end + local instanceID = unitID .. '_' .. barname + + if healthBarVBO.instanceIDtoIndex[instanceID] then + if debugmode then Spring.Echo("Trying to add duplicate bar", unitID, instanceID, barname, reason) end + return + end -- we already have this bar ! + + if unitDefID == nil or Spring.ValidUnitID(unitID) == false or Spring.GetUnitIsDead(unitID) == true then -- dead or invalid + if debugmode then + Spring.Debug.TraceEcho("Tried to add a bar to dead/invalid/nounitdef unit", unitID, unitdefID, barname) + end + return nil + end + + local effectiveScale = ((variableBarSizes and unitDefSizeMultipliers[unitDefID]) or 1.0) * barScale + + local healthBarTableCache = bt.cache + + local cp = UnitDefs[unitDefID].customParams + local healthBarHeightExtra = (cp and tonumber(cp.health_bar_height)) or 0 + -- Every element of the overlay shares one baseline: the geom positions the unit icon, the bars + -- (which stack from "icon center"), the flanking weapon bars and the status row all relative to it. + -- Half the unit height centres the whole cluster vertically on the unit's body. + healthBarTableCache[1] = (Spring.GetUnitHeight(unitID) or unitDefHeights[unitDefID]) * 0.5 + healthBarHeightExtra + if barname == 'icon' then + healthBarTableCache[4] = unitDefIconIndex[unitDefID] or 0 + local teamID = Spring.GetUnitTeam(unitID) + local r, g, b, a = Spring.GetTeamColor(teamID) + r = r or 1.0; g = g or 1.0; b = b or 1.0; a = a or 1.0 + healthBarTableCache[9] = r; healthBarTableCache[10] = g + healthBarTableCache[11] = b; healthBarTableCache[12] = a + healthBarTableCache[13] = r; healthBarTableCache[14] = g + healthBarTableCache[15] = b; healthBarTableCache[16] = a + elseif barname == 'reload' or barname == 'primarycount' or barname:sub(1, 10) == 'bustreload' then + local wc = unitDefWeaponColor[unitDefID] + local iconPath = unitDefWeaponIcon[unitDefID] + if unitDefIsComm[unitDefID] then + -- Dynamic comms have no weapons in their unitDef; the assigned weapon's defID is + -- exposed per-unit via the comm_weapon_id_1 rules param, so look it up at runtime. + local wid = Spring.GetUnitRulesParam(unitID, "comm_weapon_id_1") + local wd = wid and wid > 0 and WeaponDefs[wid] + if wd then + wc = getNormalizedWeaponColor(wd.visuals) + iconPath = wd.customParams and wd.customParams.icon + end + end + -- Radial badge icon (cache[4]): the weapon's `icon` customParam (an image path), registered on + -- demand. -1 = no icon, so the badge draws just the countdown ring with no symbol. + healthBarTableCache[4] = (iconPath and registerDynamicIcon(iconPath)) or -1 + -- Commanders always show the badge (showing "ready" below the threshold). bt.cache is shared, + -- so always (re)assign cache[5] = bartype, OR-ing the flag only for commanders. + healthBarTableCache[5] = bt.bartype + (unitDefIsComm[unitDefID] and bitAlwaysShow or 0) + -- Tint the badge with the weapon's beam color. bt.cache is shared across units, so always + -- reassign these four-component colors -- falling back to the bartype default when no color. + if wc then + healthBarTableCache[9] = wc[1]; healthBarTableCache[10] = wc[2] + healthBarTableCache[11] = wc[3]; healthBarTableCache[12] = 1.0 + healthBarTableCache[13] = wc[1]; healthBarTableCache[14] = wc[2] + healthBarTableCache[15] = wc[3]; healthBarTableCache[16] = 1.0 + else + healthBarTableCache[9] = bt.mincolor[1]; healthBarTableCache[10] = bt.mincolor[2] + healthBarTableCache[11] = bt.mincolor[3]; healthBarTableCache[12] = bt.mincolor[4] + healthBarTableCache[13] = bt.maxcolor[1]; healthBarTableCache[14] = bt.maxcolor[2] + healthBarTableCache[15] = bt.maxcolor[3]; healthBarTableCache[16] = bt.maxcolor[4] + end + elseif barname == 'reload2' or barname == 'reload3' or barname == 'reload4' then + -- Extra weapon cooldown badge. For dynamic comms the Nth weapon is runtime-assigned + -- (reload2 -> comm_weapon_id_2), so look it up live; otherwise pull from the unitDef extras list + -- (reload2 -> extras[1], reload3 -> extras[2], ...). cache is shared per bartype, so reassign per unit. + local wc, iconPath + if unitDefIsComm[unitDefID] then + local wid = Spring.GetUnitRulesParam(unitID, "comm_weapon_id_" .. tonumber(barname:sub(7))) + local wd = wid and wid > 0 and WeaponDefs[wid] + if wd then + wc = getNormalizedWeaponColor(wd.visuals) + iconPath = wd.customParams and wd.customParams.icon + end + else + local extras = unitDefExtraWeapons[unitDefID] + local extra = extras and extras[tonumber(barname:sub(7)) - 1] + wc = extra and extra.color + iconPath = extra and extra.icon + end + -- cache is shared per bartype, so always reassign: comms show this weapon "ready" below threshold too. + healthBarTableCache[5] = bt.bartype + (unitDefIsComm[unitDefID] and bitAlwaysShow or 0) + -- icon from the weapon's `icon` customParam (registered on demand); -1 = none drawn. + healthBarTableCache[4] = (iconPath and registerDynamicIcon(iconPath)) or -1 + if wc then + healthBarTableCache[9] = wc[1]; healthBarTableCache[10] = wc[2] + healthBarTableCache[11] = wc[3]; healthBarTableCache[12] = 1.0 + healthBarTableCache[13] = wc[1]; healthBarTableCache[14] = wc[2] + healthBarTableCache[15] = wc[3]; healthBarTableCache[16] = 1.0 + else + healthBarTableCache[9] = bt.mincolor[1]; healthBarTableCache[10] = bt.mincolor[2] + healthBarTableCache[11] = bt.mincolor[3]; healthBarTableCache[12] = bt.mincolor[4] + healthBarTableCache[13] = bt.maxcolor[1]; healthBarTableCache[14] = bt.maxcolor[2] + healthBarTableCache[15] = bt.maxcolor[3]; healthBarTableCache[16] = bt.maxcolor[4] + end + end + healthBarTableCache[2] = effectiveScale + healthBarTableCache[3] = range or 1 + healthBarTableCache[7] = uniformOverride or bt.uniformindex -- ability slots override the read channel + if uvOffsetOverride ~= nil then healthBarTableCache[4] = uvOffsetOverride end + if layoutSlotOverride ~= nil then healthBarTableCache[8] = layoutSlotOverride end + if alwaysShow ~= nil then healthBarTableCache[5] = bt.bartype + (alwaysShow and bitAlwaysShow or 0) end + + return pushElementInstance( + healthBarVBO, -- push into this Instance VBO Table + healthBarTableCache, + instanceID, -- this is the key inside the VBO Table, should be unique per unit + true, -- update existing element + nil, -- noupload, dont use unless you know what you want to batch push/pop + unitID) -- last one should be featureID! + -- we are returning here, to sign successful adds +end + +local function removeBarFromUnit(unitID, barname, reason) -- this will bite me in the ass later, im sure, yes it did, we need to just update them :P + local instanceKey = unitID .. "_" .. barname + if healthBarVBO.instanceIDtoIndex[instanceKey] then + if debugmode then Spring.Debug.TraceEcho(reason) end + --if barname == 'emp_damage' or barname == 'paralyze' then + -- dont decrease counter for these + --else + --end + popElementInstance(healthBarVBO, instanceKey) + end +end + +local function addBarsForUnit(unitID, unitDefID, unitTeam, unitAllyTeam, reason) -- TODO, actually, we need to check for all of these for stuff entering LOS + + if unitDefID == nil or Spring.ValidUnitID(unitID) == false or Spring.GetUnitIsDead(unitID) == true then + if debugmode then Spring.Echo("Tried to add a bar to a dead or invalid unit", unitID, "at", Spring.GetUnitPosition(unitID), reason) end + return + end + + uniformcache[1] = 0 + -- Clear only the overlay-owned floats (1-11). The gadget owns float 0 (buildprogress), float 12 (cloak) + -- and float 15 (unit height) -- zeroing 0 makes finished units render as a fresh nanoframe, zeroing 12 + -- would flicker cloak, and zeroing 15 would wipe the height the build-sweep needs. Floats 13-14 are + -- unread (their channels pack into floats 2/11), so they need no clearing. + for channels = 1, 11, 1 do + gl.SetUnitBufferUniforms(unitID, uniformcache, channels) + end + + -- This is optionally passed, and it only important in one edge case: + -- If a unit is captured and thus immediately become outside of LOS, then the getunitallyteam is still the old ally team according to getUnitAllyTEam, and not the new allyteam. + unitAllyTeam = unitAllyTeam or Spring.GetUnitAllyTeam(unitID) + + addBarForUnit(unitID, unitDefID, "health", reason) + addBarForUnit(unitID, unitDefID, "build", reason) + addBarForUnit(unitID, unitDefID, "paralyze", reason) + addBarForUnit(unitID, unitDefID, "disarm", reason) + addBarForUnit(unitID, unitDefID, "slow", reason) + -- status-duration radial badges (shown only while the effect is locked at max) + addBarForUnit(unitID, unitDefID, "paralyzetimer", reason) + addBarForUnit(unitID, unitDefID, "disarmtimer", reason) + addBarForUnit(unitID, unitDefID, "slowtimer", reason) + addBarForUnit(unitID, unitDefID, "capture", reason) + + --// ABILITY SLOTS: walk the per-unitDef assignment (same list the updater packed); each ability's bar + -- reads its slot channel via uniformOverride, presented by the kind's existing config. Modular + -- durations (reload/dgun/capture) read target-frame mod 4096; gauges/percent read 0-100 (range 100). + -- TODO: morph/goo (old ch8) not yet wired -- runtime morph slot + goo. + local abSlots = unitDefAbilitySlots[unitDefID] + if abSlots then + local threshold = options.reloadThreshold.value + local weaponN = 0 + -- Below-zone badges (jump charges / sprint / teleport) are collected, then created after the walk + -- so each can bake its centered (index, count) slot for the GS to position the whole run. + local belowList = {} + for i = 1, #abSlots do + local ab = abSlots[i] + local kind = ab.kind + local slotCh = abilitySlotChannel[i] + if kind == "reload" or kind == "commReload" or kind == "scriptReload" then + weaponN = weaponN + 1 + local cfg = (weaponN == 1) and "reload" or ("reload" .. weaponN) + if barTypeMap[cfg] then + local range, show + if kind == "commReload" then + local wid = Spring.GetUnitRulesParam(unitID, "comm_weapon_id_" .. (ab.commWeapon or 1)) + local wd = wid and wid > 0 and WeaponDefs[wid] + range = (wd and wd.reload) and (wd.reload * gameSpeed) or nil + show = (wd ~= nil) -- weapon 1 always present, 2 only if dual-equipped; shows "ready" below threshold + elseif kind == "scriptReload" then + range = unitDefScriptReload[unitDefID] + show = range and (range / gameSpeed) >= threshold + else + range = ab.reload and (ab.reload * gameSpeed) + show = (ab.reload or 0) >= threshold + end + if show then addBarForUnit(unitID, unitDefID, cfg, reason, range, slotCh) end + end + elseif kind == "burst" then + addBarForUnit(unitID, unitDefID, "bustreload" .. ab.index, reason, 100, slotCh) + elseif kind == "dgun" or kind == "moveDgun" then + addBarForUnit(unitID, unitDefID, "dgun", reason, ab.reload and (ab.reload * gameSpeed), slotCh) + elseif kind == "captureReload" then + addBarForUnit(unitID, unitDefID, "captureReload", reason, ab.reload, slotCh) + elseif kind == "shield" then addBarForUnit(unitID, unitDefID, "shield", reason, 100, slotCh) + elseif kind == "heat" then addBarForUnit(unitID, unitDefID, "heat", reason, 100, slotCh) + elseif kind == "speed" then addBarForUnit(unitID, unitDefID, "speed", reason, 100, slotCh) + elseif kind == "teleport" then belowList[#belowList + 1] = { name = "teleport", range = 4096, slotCh = slotCh } -- modular: range only gates threshold/keeps rem/range<=1; countdown comes from teleportend + elseif kind == "reammo" then belowList[#belowList + 1] = { name = "reammo", range = 1, slotCh = slotCh } -- rate-ETA band (range 1) + elseif kind == "jump" then + -- One below-zone gauge per charge: same slot, each baked with its charge index (+ charge + -- count) in uvoffset, range = reload frames. Multi-charge units always show all charges. + local charges = unitDefHasJump[unitDefID] or 1 + local reloadFrames = unitDefJumpReloadFrames[unitDefID] or 1 + for c = 0, charges - 1 do + local nm = (c == 0) and "jump" or ("jump" .. (c + 1)) + if barTypeMap[nm] then + belowList[#belowList + 1] = { + name = nm, range = reloadFrames, slotCh = slotCh, + uvoffset = c + charges * 16, alwaysShow = (charges > 1), + } + end + end + elseif kind == "moveAbility" then belowList[#belowList + 1] = { name = "ability", range = 4096, slotCh = slotCh } -- modular; countdown from the ready-frame + elseif kind == "stockProg" then addBarForUnit(unitID, unitDefID, "stockpile", reason, 1, slotCh) -- rate-ETA band (range 1) + elseif kind == "stockCnt" then addBarForUnit(unitID, unitDefID, "stockpilecount", reason, 1, slotCh) + elseif kind == "goo" then belowList[#belowList + 1] = { name = "goo", range = 1, slotCh = slotCh } -- pausable-ETA band (range 1) + end + end + -- Create the below-zone badges, baking each one's centered slot: .w = index | (count << 4). + local belowCount = #belowList + unitDefBelowCount[unitDefID] = belowCount + -- bitmask of the distinct below-badge channels (for the morph's visible-count re-centering). + local belowMask, seenCh = 0, {} + for _, b in ipairs(belowList) do + if b.slotCh and not seenCh[b.slotCh] then seenCh[b.slotCh] = true; belowMask = belowMask + 2 ^ b.slotCh end + end + unitDefBelowMask[unitDefID] = belowMask + -- DEBUG (remove later): trace the Detriment's ability slots + below-badge collection. + if UnitDefNames and UnitDefNames["striderdetriment"] and unitDefID == UnitDefNames["striderdetriment"].id then + local kinds = {} + for _, ab in ipairs(abSlots) do kinds[#kinds + 1] = ab.kind end + local names = {} + for _, b in ipairs(belowList) do names[#names + 1] = b.name end + Spring.Echo(string.format("[Overlay DBG] Detriment hasJump=%s reloadFrames=%s | abilitySlots=[%s] | belowList=[%s] belowCount=%d", + tostring(unitDefHasJump[unitDefID]), tostring(unitDefJumpReloadFrames[unitDefID]), + table.concat(kinds, ","), table.concat(names, ","), belowCount)) + end + -- /DEBUG + for idx = 1, belowCount do + local b = belowList[idx] + addBarForUnit(unitID, unitDefID, b.name, reason, b.range, b.slotCh, + b.uvoffset, (idx - 1) + belowCount * 16, b.alwaysShow) + end + end + -- The unit icon is now part of the WG.icons cluster (icon + rank + group on one quad), owned by + -- relayoutUnitIcons. Mark the unit dirty so it (re)builds the icon instance on the next DrawWorld; + -- this also re-pushes it after a VBO wipe, since addBarsForUnit re-runs for every visible unit then. + wgDirtyUnits[unitID] = true +end + +local function removeBarsFromUnit(unitID, reason) + for barname, v in pairs(barTypeMap) do + removeBarFromUnit(unitID, barname, reason) + end +end + +local function addBarToFeature(featureID, barname) + if debugmode then Spring.Debug.TraceEcho() end + local featureDefID = Spring.GetFeatureDefID(featureID) + + local bt = barTypeMap[barname] + + if featureVBO.instanceIDtoIndex[featureID] then return end -- already exists, bail + + pushElementInstance( + featureVBO, -- push into this Instance VBO Table + {featureDefHeights[featureDefID] + additionalheightaboveunit, -- height + 1.0 * barScale, -- size mult + 1.0, -- timer end + bt.cache[4], -- atlas cell (fill start / badge icon), resolved post-buildIconAtlas + + bt.bartype, -- bartype int + 0, -- bar index (how manyeth per unit) + bt.uniformindex, -- ssbo location offset (> 20 for health) + bt.layoutSlot or 0, -- layout slot (rides bartype_index_ssboloc.w) + + bt.mincolor[1], bt.mincolor[2], bt.mincolor[3], bt.mincolor[4], + bt.maxcolor[1], bt.maxcolor[2], bt.maxcolor[3], bt.maxcolor[4], + 0, 0, 0, 0}, -- these are just padding zeros for instData, that will get filled in + featureID .. "_" .. barname, -- this is the key inside the VBO Table, should be unique per unit + true, -- update existing element + nil, -- noupload, dont use unless you know what you want to batch push/pop + featureID) -- last one should be featureID! +end + +local function removeBarFromFeature(featureID, barname) + local instanceKey = featureID .. "_" .. barname + if featureVBO.instanceIDtoIndex[instanceKey] then + popElementInstance(featureVBO, instanceKey) + end +end + +function init() -- assigns the forward-declared upvalue (see top of file) + clearInstanceTable(healthBarVBO) + requeueAllIcons() + + for i, unitID in ipairs(Spring.GetAllUnits()) do -- gets radar blips too! + -- probably shouldnt be adding non-visible units + + if fullview then + addBarsForUnit(unitID, Spring.GetUnitDefID(unitID), Spring.GetUnitTeam(unitID), nil, 'initfullview') + else + local losstate = Spring.GetUnitLosState(unitID, myAllyTeamID) + if losstate.los then + addBarsForUnit(unitID, Spring.GetUnitDefID(unitID), Spring.GetUnitTeam(unitID), nil, 'initlos') + --Spring.Echo(unitID, "IS in los") + else + --Spring.Echo(unitID, "is not in los for ", myAllyTeamID) + end + end + end + +end + +local function addFeature(featureID) + -- some map-supplied features dont have a model, in these cases modelpath == "" + local featureDefID = Spring.GetFeatureDefID(featureID) + if FeatureDefs[featureDefID].name ~= 'geovent' and FeatureDefs[featureDefID].modelpath ~= '' then + addBarToFeature(featureID, 'featureresurrect') + addBarToFeature(featureID, 'featurereclaim') + + if options.drawFeatureHealth.value then + addBarToFeature(featureID, 'featurehealth') + end + end +end + +local function removeFeature(featureID) + removeBarFromFeature(featureID, 'featureresurrect') + removeBarFromFeature(featureID, 'featurereclaim') + removeBarFromFeature(featureID, 'featurehealth') +end + +local GetVisibleFeatures = Spring.GetVisibleFeatures +local GetFeatureDefID = Spring.GetFeatureDefID + +function initfeaturebars() + clearInstanceTable(featureVBO) + + local currentWidget = widget:GetInfo().name + + WG.GlUnionUpdaterAddFeatureCallbacks = WG.GlUnionUpdaterAddFeatureCallbacks or {} + WG.GlUnionUpdaterRemoveFeatureCallbacks = WG.GlUnionUpdaterRemoveFeatureCallbacks or {} + + WG.GlUnionUpdaterAddFeatureCallbacks[currentWidget] = addFeature + WG.GlUnionUpdaterRemoveFeatureCallbacks[currentWidget] = removeFeature + + local visibleFeatures = GetVisibleFeatures(-1, nil, false, false) + + local cnt = #visibleFeatures + for i = cnt, 1, -1 do + featureID = visibleFeatures[i] + featureDefID = GetFeatureDefID(featureID) or -1 + if FeatureDefs[featureDefID].destructable and FeatureDefs[featureDefID].drawTypeString == "model" then + addFeature(featureID) + end + end +end + +--12:32 PM] Beherith: widget:PlayerChanged generalizations +--[12:33 PM] Beherith: So, I would like to ask if we have a general guideline or if @Floris knows anything about what circumstances should trigger UI GFX widget reinitialization +--[12:36 PM] Beherith: Here, I assume we can live with a few assumptions: +--1. UI GFX widgets are LOS dependent things, that either +-- A. Should look the same for all players on an ALLYteam +-- B. Could look different for each member of an ALLYTeam +--2. Always render different things for different ALLYteams +--This presents and interesting state for most widgets especially for SPECFULLVIEW +--Obviously, the biggest reason for needing to abstract this is to avoid boilerplate mistakes for most new GL4 widgets, which are --stateful, unlike most previous widgets (most of which collected things they wanted to draw every frame) +--[12:39 PM] Beherith: So I assume widget:PlayerChanged gets called on any legal player change, and should keep track of the following: +--1. spectating state +--2. specfullview state +--3. myAllyTeamID +--4. myTeamID +--[12:40 PM] Beherith: There are 3 real states someone can be in: +--1. player +--2. spectator no fullview +--3. spectator with fullview + +--(excluding godmode /globallos et al) +--[12:40 PM] Beherith: Transitions between any of the above 3 should trigger a full reinit +--[12:41 PM] Beherith: But some internal transitions, for stuff that is draw differently for allies might require additional checks, for spectators who have fullview off? + +local function FeatureReclaimStartedHealthbars (featureID, step) -- step is negative for reclaim, positive for resurrect + --Spring.Echo("FeatureReclaimStartedHealthbars", featureID) + + --gl.SetFeatureBufferUniforms(featureID, 0.5, 2) -- update GL +end + +local function UnitCaptureStartedHealthbars(unitID, step) -- step is negative for reclaim, positive for resurrect + --TODO +end + +--function widget:UnitDamaged(unitID, unitDefID, unitTeam, damage, paralyzer) +local function UnitParalyzeDamageHealthbars(unitID, unitDefID, damage) + -- TODO +end + +local function ProjectileCreatedReloadHB(projectileID, unitID, weaponID, unitDefID) + --TODO + --local unitDefID = Spring.GetUnitDefID(unitID) + + --updateReloadBar(unitID, unitDefID, 'ProjectileCreatedReloadHB') +end + +function MorphUpdate(morphTable) + for unitID, morph in pairs(morphTable) do + if not healthBarVBO.instanceIDtoIndex[unitID .. "_morph"] then + -- Bake the unit's persistent below-badge count so morph centers within that run (idx 0). + local mudef = Spring.GetUnitDefID(unitID) + local p = (unitDefBelowCount[mudef] or 0) * 16 + (unitDefBelowMask[mudef] or 0) * 256 -- P (bits 4-7) + below-channel mask + addBarForUnit(unitID, nil, "morph", "MorphUpdate", nil, nil, nil, p) + end + end + for _, callback in pairs(WG.MorphUpdateCallbacks) do + callback(morphTable) + end +end + +function MorphStart(unitID, morphDef) + local mudef = Spring.GetUnitDefID(unitID) + local p = (unitDefBelowCount[mudef] or 0) * 16 + (unitDefBelowMask[mudef] or 0) * 256 -- P (bits 4-7) + below-channel mask (bits 8+) + addBarForUnit(unitID, nil, "morph", "MorphStart", nil, nil, nil, p) + for _, callback in pairs(WG.MorphStartCallbacks) do + callback(unitID, morphDef) + end +end + +function MorphStopOrFinished(unitID) + removeBarFromUnit(unitID, "morph", "MorphStopOrFinished") + for _, callback in pairs(WG.MorphStopCallbacks) do + callback(unitID) + end +end + +function widget:Initialize() + if not gl.CreateShader then -- no shader support, so just remove the widget itself, especially for headless + widgetHandler:RemoveWidget() + return + end + WG['unitoverlay'] = {} + WG['unitoverlay'].getScale = function() + return barScale + end + WG['unitoverlay'].setScale = function(value) + barScale = value + init() + initfeaturebars() + end + WG['unitoverlay'].getVariableSizes = function() + return variableBarSizes + end + WG['unitoverlay'].setVariableSizes = function(value) + variableBarSizes = value + init() + initfeaturebars() + end + WG['unitoverlay'].getDrawWhenGuiHidden = function() + return drawWhenGuiHidden + end + WG['unitoverlay'].setDrawWhenGuiHidden = function(value) + drawWhenGuiHidden = value + end + + initGL4() + + -- TODO: dont even bother drawing health bars for features that were present on frame 0 - no point in doing so + -- This is stuff like trees and map features, and scenario features + init() + initfeaturebars() + widgetHandler:RegisterGlobal("FeatureReclaimStartedHealthbars", FeatureReclaimStartedHealthbars ) + widgetHandler:RegisterGlobal("UnitCaptureStartedHealthbars", UnitCaptureStartedHealthbars ) + widgetHandler:RegisterGlobal("UnitParalyzeDamageHealthbars", UnitParalyzeDamageHealthbars ) + widgetHandler:RegisterGlobal("ProjectileCreatedReloadHB", ProjectileCreatedReloadHB ) + + WG.MorphUpdateCallbacks = WG.MorphUpdateCallbacks or {} + WG.MorphStartCallbacks = WG.MorphStartCallbacks or {} + WG.MorphStopCallbacks = WG.MorphStopCallbacks or {} + + --// link morph callins + widgetHandler:RegisterGlobal('MorphUpdate', MorphUpdate) + widgetHandler:RegisterGlobal('MorphFinished', MorphStopOrFinished) + widgetHandler:RegisterGlobal('MorphStart', MorphStart) + widgetHandler:RegisterGlobal('MorphStop', MorphStopOrFinished) + + --// deactivate cheesy progress text + widgetHandler:RegisterGlobal('MorphDrawProgress', function() return true end) + + stateCtl.applyAll() -- push initial state-icon visibility (single source of truth) +end + +-- Track Shift for the Shift-gated state icons. Return nil so the key still propagates. +function widget:KeyPress(key, mods, isRepeat) + stateCtl.refreshShift() +end + +function widget:KeyRelease(key, mods) + stateCtl.refreshShift() +end + +-- DEBUG: blit the runtime icon atlas to the center of the screen so atlas/cell layout bugs are +-- inspectable live. Toggled by the 'debugDrawAtlas' option; no-op until the atlas exists. +function widget:DrawScreen() + if not options.debugDrawAtlas.value or not iconAtlasTexture then return end + local vsx, vsy = gl.GetViewSizes() + local atlasW = iconAtlasCols * iconAtlasCellSize + local atlasH = iconAtlasRows * iconAtlasCellSize + -- Fit within 90% of screen height, preserving the atlas's aspect ratio. + local h = vsy * 0.9 + local w = h * (atlasW / atlasH) + local cx, cy = vsx * 0.5, vsy * 0.5 + local x1, y1 = cx - w * 0.5, cy - h * 0.5 + local x2, y2 = cx + w * 0.5, cy + h * 0.5 + -- Checkerboard-free dark backdrop so transparent/empty cells read as black, plus a border. + gl.Color(0, 0, 0, 0.85) + gl.Rect(x1, y1, x2, y2) + gl.Color(1, 1, 1, 1) + gl.Texture(iconAtlasTexture) + -- Flip T so cell 0 (atlas-space bottom row) draws at the top, reading 0..N top-to-bottom. + gl.TexRect(x1, y1, x2, y2, false, true) + gl.Texture(false) + gl.Color(1, 1, 1, 1) +end + +function widget:Shutdown() + WG.UnitOverlayIconAtlas = nil + if iconAtlasTexture then gl.DeleteTexture(iconAtlasTexture); iconAtlasTexture = nil end + -- Release the GL4 buffers so a /luaui reload starts from a clean slate instead of leaking the + -- previous instance's VBO/VAO (which can leave reload in an inconsistent, toggling state). + if healthBarVBO and healthBarVBO.Delete then healthBarVBO:Delete() end + if featureVBO and featureVBO.Delete then featureVBO:Delete() end + widgetHandler:DeregisterGlobal("FeatureReclaimStartedHealthbars" ) + widgetHandler:DeregisterGlobal("UnitCaptureStartedHealthbars" ) + widgetHandler:DeregisterGlobal("UnitParalyzeDamageHealthbars" ) + widgetHandler:DeregisterGlobal("ProjectileCreatedReloadHB" ) + Spring.Echo("Healthbars GL4 unloaded hooks") + + widgetHandler:DeregisterGlobal('MorphUpdate') + widgetHandler:DeregisterGlobal('MorphFinished') + widgetHandler:DeregisterGlobal('MorphStart') + widgetHandler:DeregisterGlobal('MorphStop') + + widgetHandler:DeregisterGlobal('MorphDrawProgress') + + local currentWidget = widget:GetInfo().name + WG.GlUnionUpdaterAddFeatureCallbacks[currentWidget] = nil + WG.GlUnionUpdaterRemoveFeatureCallbacks[currentWidget] = nil +end + +function widget:RecvLuaMsg(msg, playerID) + if msg:sub(1,18) == 'LobbyOverlayActive' then + chobbyInterface = (msg:sub(1,19) == 'LobbyOverlayActive1') + end +end + +--[[ +function widget:UnitCreated(unitID, unitDefID, teamID) + addBarsForUnit(unitID, unitDefID, teamID, nil, 'UnitCreated') +end + +function widget:UnitDestroyed(unitID, unitDefID, teamID) + if debugmode then Spring.Echo("HBGL4:UnitDestroyed",unitID, unitDefID, teamID) end + removeBarsFromUnit(unitID,'UnitDestroyed') + onUnitIconHolderReset(unitID) +end + +function widget:UnitFinished(unitID, unitDefID, teamID) -- reset bars on construction complete? + widget:UnitDestroyed(unitID, unitDefID, teamID) + widget:UnitCreated(unitID, unitDefID, teamID) +end + +function widget:UnitEnteredLos(unitID, unitTeam, allyTeam, unitDefID) -- this is still called when in spectator mode :D + if not fullview then addBarsForUnit(unitID, Spring.GetUnitDefID(unitID), unitTeam, nil, 'UnitEnteredLos') end +end + +function widget:UnitLeftLos(unitID, unitTeam, allyTeam, unitDefID) + if spec and fullview then return end -- Interesting bug: if we change to spec with /spectator 1, then we receive unitLeftLos callins afterwards :P + removeBarsFromUnit(unitID, 'UnitLeftLos') +end + +function widget:UnitTaken(unitID, unitDefID, oldTeamID, newTeamID) + local newAllyTeamID = select( 6, Spring.GetTeamInfo(newTeamID)) + + if debugmode then + Spring.Echo("widget:UnitTaken",unitID, unitDefID, oldTeamID, newTeamID, Spring.GetUnitAllyTeam(unitID),newAllyTeamID) + end + + removeBarsFromUnit(unitID,'UnitTaken') -- because taken units dont actually call unitleftlos :D + if newAllyTeamID == myAllyTeamID then -- but taken units, that we see being taken trigger unitenteredlos on the same frame + addBarsForUnit(unitID, unitDefID, newTeamID, newAllyTeamID, 'UnitTaken') + end +end + +function widget:UnitGiven(unitID, unitDefID, newTeamID) + --Spring.Echo("widget:UnitGiven",unitID, unitDefID, newTeamID) + removeBarsFromUnit(unitID, 'UnitGiven') + addBarsForUnit(unitID, unitDefID, newTeamID, nil, 'UnitTaken') +end +]]-- + +function widget:VisibleUnitAdded(unitID, unitDefID, unitTeam) + addBarsForUnit(unitID, unitDefID, unitTeam, nil, 'VisibleUnitAdded') +end + +function widget:VisibleUnitRemoved(unitID) + removeBarsFromUnit(unitID, 'VisibleUnitRemoved') + -- Pop the icon-cluster VBO instances immediately so a recycled unitID doesn't + -- inherit a stale icon from the previous occupant. Clear all per-unit state and + -- cancel any pending relayout so the old ID is fully gone before the engine may + -- reuse it for a new unit. + onUnitIconHolderReset(unitID) + wgUnitIcons[unitID] = nil + wgUnitGroup[unitID] = nil + wgUnitCommand[unitID] = nil + wgDirtyUnits[unitID] = nil +end + +function widget:VisibleUnitsChanged(extVisibleUnits, extNumVisibleUnits) + spec, fullview = Spring.GetSpectatingState() + myTeamID = Spring.GetMyTeamID() + myAllyTeamID = Spring.GetMyAllyTeamID() + myPlayerID = Spring.GetMyPlayerID() + + clearInstanceTable(healthBarVBO) -- clear all instances + requeueAllIcons() + for unitID, unitDefID in pairs(extVisibleUnits) do + addBarsForUnit(unitID, unitDefID, Spring.GetUnitTeam(unitID), nil, "VisibleUnitsChanged") -- TODO: add them with noUpload = true + end + --uploadAllElements(healthBarVBO) -- upload them all +end + +function widget:PlayerChanged(playerID) + + local currentspec, currentfullview = Spring.GetSpectatingState() + local currentTeamID = Spring.GetMyTeamID() + local currentAllyTeamID = Spring.GetMyAllyTeamID() + local currentPlayerID = Spring.GetMyPlayerID() + local reinit = false + + if debugmode then Spring.Echo("HBGL4 widget:PlayerChanged",'spec', currentspec, 'fullview', currentfullview, 'teamID', currentTeamID, 'allyTeamID', currentAllyTeamID, "playerID", currentPlayerID) end + + -- cases where we need to trigger: + if (currentspec ~= spec) or -- we transition from spec to player, yes this is needed + (currentfullview ~= fullview) or -- we turn on or off fullview + ((currentAllyTeamID ~= myAllyTeamID) and not currentfullview) -- our ALLYteam changes, and we are not in fullview + --((currentTeamID ~= myTeamID) and not currentfullview) + + then + -- do the actual reinit stuff, but first change my own + reinit = true + if debugmode then Spring.Echo("HBGL4 triggered a playerchanged reinit") end + + end + -- save the state: + spec = currentspec + fullview = currentfullview + myAllyTeamID = currentAllyTeamID + myTeamID = currentTeamID + myPlayerID = currentPlayerID + --if reinit then init() end +end + + +function widget:GameFrame(gameFrame) + if gameFrame % 15 == 0 then + refreshGroupNumbers() -- poll control-group membership for the bottom-right corner badge + end + if gameFrame % 15 == 7 then + refreshUnitCommands() -- poll current command for the bottom-left corner badge (offset to spread load) + end + if debugmode then + locateInvalidUnits(healthBarVBO) + end +end + +function widget:DrawWorld() + --Spring.Echo(Engine.versionFull ) + if chobbyInterface then return end + if not drawWhenGuiHidden and Spring.IsGUIHidden() then return end + + renderIconAtlas() + flushPendingIcons() -- blit any newly-registered WG.icons textures into the atlas + processDirtyIcons() -- (re)push hovering-icon instances for units whose icons changed + local disticon = Spring.GetConfigInt("UnitIconDistance", 200) * 27.5 -- iconLength = unitIconDist * unitIconDist * 750.0f; + -- "Draw on top" can't use a depth-buffer clear (the engine ignores it in DrawWorld), so instead the + -- shader squeezes the overlay into a near-plane sliver (overlayDepthBand) -- it then wins the depth + -- test against the world while still depth-writing so overlays sort among themselves. + gl.DepthTest(true) + gl.DepthMask(true) + -- Standard alpha blending for the overlay (the atlas blits above left blending disabled). + gl.Blending(GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA) + gl.Texture(1, iconAtlasTexture) + healthBarShader:Activate() + healthBarShader:SetUniform("iconDistance",disticon) + healthBarShader:SetUniform("overlayDepthBand", 0.05) + if not debugmode then + local fd = options.fadeDistance.value + healthBarShader:SetUniform("cameraDistanceMult", fd > 0 and (shaderConfig.BARFADESTART / fd) or 0.0) + end + local sfd = options.statusFadeDistance.value + healthBarShader:SetUniform("statusFadeDistance", sfd) + healthBarShader:SetUniform("iconHideDistance", options.iconHideDistance.value) + healthBarShader:SetUniform("cameraDistanceMultGlyph", glphydistmult) + healthBarShader:SetUniform("skipGlyphsNumbers",skipGlyphsNumbers) --0.0 is everything, 1.0 means only numbers, 2.0 means only bars, + healthBarShader:SetUniform("vbarUserX", options.weaponBarOffset.value) + healthBarShader:SetUniform("vbarSize", options.weaponBarSize.value) + healthBarShader:SetUniform("iconSize", options.unitIconSize.value) + healthBarShader:SetUniform("barBorderWidth", options.barBorder.value) + healthBarShader:SetUniform("trackDarken", options.trackDarken.value) + healthBarShader:SetUniform("reloadThreshold", options.reloadThreshold.value) + healthBarShader:SetUniform("digitAtlasStart", digitAtlasStartIndex) + healthBarShader:SetUniform("jumpIconCell", jumpIconAtlasIndex) + healthBarShader:SetUniform("rowOffset", options.statusHeight.value) + healthBarShader:SetUniform("rowSize", options.statusSize.value) + healthBarShader:SetUniform("rowSpacing", options.statusSpacing.value) + healthBarShader:SetUniform("overallScale", options.overallScale.value) + healthBarShader:SetUniform("barSize", options.barSize.value) + healthBarShader:SetUniform("barOffset", options.barHeightAboveUnit.value) + healthBarShader:SetUniform("barSpacing", options.barSpacing.value) + healthBarShader:SetUniform("belowBadgeHeight", options.abilityBadgeHeight.value) + -- flashing icons: smooth oscillating alpha (mirrors legacy unit_icons iconFade) + healthBarShader:SetUniform("pulseAlpha", 0.35 + 0.65 * (0.5 + 0.5 * math.sin(os.clock() * 5.0))) + healthBarShader:SetUniform("isFeature", 0) + if healthBarVBO.usedElements > 0 then + healthBarVBO.VAO:DrawArrays(GL.POINTS,healthBarVBO.usedElements) + end + -- below its the feature bars being drawn: + healthBarShader:SetUniform("cameraDistanceMultGlyph", glyphdistmultfeatures) + healthBarShader:SetUniform("isFeature", 1) -- feature status channels differ; keep their fixed badge layout + if featureVBO.usedElements > 0 then + if not debugmode then healthBarShader:SetUniform("cameraDistanceMult",featureResurrectDistMult) end + featureVBO.VAO:DrawArrays(GL.POINTS,featureVBO.usedElements) + end + + healthBarShader:Deactivate() + gl.Texture(1, false) + gl.DepthTest(false) + gl.DepthMask(false) --"BK OpenGL state resets", reset to default state +end + +function widget:TextCommand(command) + if string.find(command, "debugunitoverlay", nil, true) == 1 then + debugmode = not debugmode + Spring.Echo("Debug mode for Unit Overlay GL4 set to", debugmode) + healthBarVBO.debug = debugmode + end +end + +function widget:GetConfigData(data) + return { + barScale = barScale, + barHeight = barHeight, + variableBarSizes = variableBarSizes, + drawWhenGuiHidden = drawWhenGuiHidden, + skipGlyphsNumbers = skipGlyphsNumbers, + } +end + +function widget:SetConfigData(data) + barScale = data.barScale or barScale + if data.variableBarSizes ~= nil then + variableBarSizes = data.variableBarSizes + end + if data.drawWhenGuiHidden ~= nil then + drawWhenGuiHidden = data.drawWhenGuiHidden + end + if data.barHeight ~= nil then + barHeight = data.barHeight + shaderSourceCache.shaderConfig.BARHEIGHT = barHeight + shaderSourceCache.shaderConfig.BARCORNER = 0.06 + (shaderConfig.BARHEIGHT / 9) + shaderSourceCache.shaderConfig.SMALLERCORNER = shaderConfig.BARCORNER * 0.6 + end + if data.skipGlyphsNumbers ~= nil then + skipGlyphsNumbers = data.skipGlyphsNumbers + end +end diff --git a/LuaUI/Widgets/unit_auto_group.lua b/LuaUI/Widgets/unit_auto_group.lua index 951b207426..9db59b746d 100644 --- a/LuaUI/Widgets/unit_auto_group.lua +++ b/LuaUI/Widgets/unit_auto_group.lua @@ -33,7 +33,7 @@ end local hotkeyPath = 'Hotkeys/Selection/Control Groups' i18nPrefix = 'autogroup_' -options_order = { 'mainlabel', 'text_hotkey', 'cleargroups', 'removefromgroup', 'loadgroups', 'addall', 'verbose', 'immediate', 'groupnumbers', } +options_order = { 'mainlabel', 'text_hotkey', 'cleargroups', 'removefromgroup', 'loadgroups', 'addall', 'verbose', 'immediate', } options_path = 'Settings/Interface/Control Groups' options = { mainlabel = {name='Auto Group', type='label'}, @@ -63,12 +63,10 @@ options = { value = false, noHotkey = true, }, - groupnumbers = { -- FIXME why is this handled by autogroups? it's standalone functionality - type = 'bool', - value = true, - noHotkey = true, - }, - + -- Group-number drawing is now owned by the Unit Overlay GL4 widget (the "Control group number" + -- toggle under Settings/Interface/Unit Overlay/Unit States), which draws it as a corner badge on + -- the unit icon. The old standalone gl.Text drawing here has been removed. + text_hotkey = { type = 'text', path = hotkeyPath, @@ -170,28 +168,6 @@ function widget:Initialize() myTeam = team end -function widget:DrawWorld() - if not IsGuiHidden() then - local existingGroups = GetGroupList() - if options.groupnumbers.value then - for inGroup, _ in pairs(existingGroups) do - local units = GetGroupUnits(inGroup) - for _, unit in ipairs(units) do - if Spring.IsUnitInView(unit) then - local ux, uy, uz = Spring.GetUnitViewPosition(unit) - gl.PushMatrix() - gl.Translate(ux, uy, uz) - gl.Billboard() - gl.Color(textColor)--unused anyway when gl.Text have option 's' (and b & w) - gl.Text("" .. inGroup, 30.0, -10.0, textSize, "cns") - gl.PopMatrix() - end - end - end - end - end -end - local function SetGroupFromAuto(unitID, unitDefID) local autoGroup = unit2group[unitDefID] if not autoGroup then diff --git a/LuaUI/Widgets/unit_gl_uniform_updater.lua b/LuaUI/Widgets/unit_gl_uniform_updater.lua new file mode 100644 index 0000000000..0567e59ea8 --- /dev/null +++ b/LuaUI/Widgets/unit_gl_uniform_updater.lua @@ -0,0 +1,615 @@ +function widget:GetInfo() + return { + name = "Unit gl uniform updater", + desc = "Maintains unit and feature GL uniforms", + author = "Amnykon", + date = "Jan 2025", + license = "GNU GPL v2 or later", + layer = -100, + enabled = true + } +end + +local ceil = math.ceil +local updateCount = 0 + +----------------------------------------------------------------- +-- Units +----------------------------------------------------------------- + +local empDecline = 1 / Game.paralyzeDeclineRate +local gameSpeed = Game.gameSpeed +local paralyzeOnMaxHealth = Game.paralyzeOnMaxHealth + +-- Spent silo missiles set paralyze to a 99999999 sentinel (scripts/cruisemissile.lua) to freeze the +-- now-hidden, off-map unit. Real units never approach this, so treat it as "no bar" rather than max EMP. +local maxRealPara = 1e7 +local myAllyTeamID = Spring.GetMyAllyTeamID() + +-- Slow: MAX_SLOW_FACTOR (the 50% cap) is published by unit_timeslow.lua as a game rules param. +-- slowState above the cap = "overslow" (pinned at max while the excess decays). The excess decays +-- at DEGRADE_FACTOR = 0.04 slowState/second (LuaRules/Configs/timeslow_defs.lua), so the time still +-- locked at max = (slowState - cap) / 0.04 seconds. +local maxSlowFactor = Spring.GetGameRulesParam("MAX_SLOW_FACTOR") or 0.5 +local slowDecayPerSecond = 0.04 + + +local includeDir = "LuaUI/Widgets/Include/" +VFS.Include(includeDir.."gl_uniform_channels.lua") + +local GetUnitDefID = Spring.GetUnitDefID +local GetUnitIsStunned = Spring.GetUnitIsStunned +local GetUnitHealth = Spring.GetUnitHealth +local GetUnitWeaponState = Spring.GetUnitWeaponState +local GetUnitShieldState = Spring.GetUnitShieldState +local GetUnitStockpile = Spring.GetUnitStockpile +local GetUnitRulesParam = Spring.GetUnitRulesParam +local glSetUnitBufferUniforms = gl.SetUnitBufferUniforms + +local unitUpdateRate = 10 +local units = {} +local unitsCount = 0 +local unitPosition = {} +local currentUnit = 1 + +-- Status channels (paralyze/disarm) pack into 12-bit fields: 1-100 = magnitude (value*100), 100+secs = +-- locked-at-max duration. Mirrors the old float semantics (<=1 magnitude, >1 = 1+seconds), which the +-- shader's readField status-decode reproduces, so downstream consumers are unchanged. +local mfloor = math.floor +-- Status (paralyze/disarm/slow) field: 0-100 = magnitude (charging), >=200 = locked at max, storing the +-- effect-END frame (mod 3895, +200 offset) so the duration badge counts down SMOOTHLY on the GPU instead +-- of stepping a whole second at the round-robin rate. (v-1 = seconds of lock remaining.) The magnitude +-- bar that shares this channel just clamps the big value to a full bar, so it's unaffected. +local STATUS_LOCK_BASE = 200 +local STATUS_LOCK_MOD = 3895 -- 200 + 3895 = 4095 (12-bit cap); covers ~130s of lock +local function encodeStatus(v, gameFrame) + if v <= 0 then return 0 end + if v <= 1 then return mfloor(v * 100 + 0.5) end + return STATUS_LOCK_BASE + (mfloor(gameFrame + (v - 1) * gameSpeed) % STATUS_LOCK_MOD) +end + +-- Encode one ability slot into a 12-bit int per its kind: modular = ready-frame mod 4096; percent = +-- 0-100; int = count. (morph is runtime-assigned, handled separately.) Folds the old per-ability value +-- computations under one switch; the bar's BARTYPE drives the shader decode. +local mmin = math.min +local function pct100(v) return mfloor((v < 0 and 0 or (v > 1 and 1 or v)) * 100 + 0.5) end +-- Modular cooldown: store the ready-frame mod 4096; a ready/past frame stores 0 (bar hides, no aliasing). +local function modFrame(frame, now) return (frame and frame > now) and (frame % 4096) or 0 end +-- Jump: ONE value drives every charge badge -- the frame the LAST charge finishes. The Detriment's +-- 3*reload can exceed the 4096-frame modular window, so it's stored at 1/8 frame resolution (the shader +-- decode in UnitOverlayGL4 must use the same JUMP_FRAME_SCALE). 0 = fully charged (all badges full; a +-- single-charge unit's badge hides). Recomputed from the live jumpReload each visit so it self-corrects. +local JUMP_FRAME_SCALE = 8 + +local RATE_ETA_MAX_SECS = 256 +local morphRateLast, reammoRateLast, stockRateLast, gooRateLast = {}, {}, {}, {} + +-- Pausable ETA (morph / reammo / stockpile / goo): SMOOTH while advancing, truly FROZEN (no creep) when stopped. +-- While progress is advancing it stores an absolute completion frame (>= PAUSE_FRAME_BASE) so the shader +-- counts it down smoothly at the observed (metal-fed) rate; while stopped it stores a STATIC seconds band +-- (2+secs, time-independent) so the needle holds still instead of drifting. The /2 scale must match the +-- shader decode. Used for goo (Puppy replication next to metal). +local PAUSE_FRAME_SCALE = 2 +local PAUSE_FRAME_BASE = 2048 +local PAUSE_GRACE = 60 -- frames of no observed progress before freezing (bridges the source's update steps) + +local function frameModeBand(completionFrame) + return PAUSE_FRAME_BASE + mfloor(completionFrame / PAUSE_FRAME_SCALE) % PAUSE_FRAME_BASE +end +local function staticBand(remFrames) + local secs = mfloor(remFrames / gameSpeed + 0.5) + if secs > RATE_ETA_MAX_SECS then secs = RATE_ETA_MAX_SECS elseif secs < 0 then secs = 0 end + return 2 + secs +end + +-- nominalFrames: full-rate frames to complete (e.g. goo's cost/drain) = count "as if it had metal". The +-- needle ONLY moves while progress is actually advancing (a grace window bridges the source's update +-- steps and the overlay's round-robin); the first visit never counts (it could be a just-out-of-ammo +-- bomber). When stopped (flying back, disabled/unpowered pad, out of resources) the remaining is held +-- static, captured once so it doesn't creep. When omitted, the rate is inferred from progress deltas. +-- forceShow: show the badge even at prog == 0 (caller already gated visibility, e.g. bomber needs rearm). +local function pausableETABand(store, unitID, prog, gameFrame, nominalFrames, forceShow) + if prog >= 1 then store[unitID] = nil; return 0 end -- complete -> hidden + if not forceShow and prog <= 0 then store[unitID] = nil; return 0 end -- inactive -> hidden + local last = store[unitID] + if nominalFrames then + local completion, progFrame + if last and prog > last.prog then -- real, observed progress -> (re)anchor and mark working + completion = gameFrame + (1 - prog) * nominalFrames + progFrame = gameFrame + elseif last then -- no progress this visit: keep the anchor and the last-progress timestamp + completion = last.completion + progFrame = last.progFrame + else -- first visit: anchor, but DON'T mark progress (so a just-stopped/idle badge stays frozen) + completion = gameFrame + (1 - prog) * nominalFrames + end + if progFrame and (gameFrame - progFrame <= PAUSE_GRACE) then -- recently advancing -> gauge moves + store[unitID] = { prog = prog, completion = completion, progFrame = progFrame } + return frameModeBand(completion) + end + -- stopped: hold the remaining it had reached (captured once so it doesn't creep down) + local frozenVal = (last and last.frozenVal) or staticBand(completion - gameFrame) + store[unitID] = { prog = prog, completion = completion, progFrame = progFrame, frozenVal = frozenVal } + return frozenVal + end + -- Inferred-rate path (morph / stockpile): no known nominal time. + local rate = (last and last.rate) or 0 + local advancing = last and prog > last.prog + if advancing then + local inst = (prog - last.prog) / (gameFrame - last.frame) + rate = (rate > 0) and (rate * 0.7 + inst * 0.3) or inst -- don't EMA up from a zero seed + end + store[unitID] = { prog = prog, frame = gameFrame, rate = rate } + if rate <= 1e-7 then return 1 end -- no rate known yet -> grey constant (brief) + local framesLeft = (1 - prog) / rate + if advancing and framesLeft < PAUSE_FRAME_BASE * PAUSE_FRAME_SCALE then + return frameModeBand(gameFrame + framesLeft) + end + return staticBand(framesLeft) +end + +local function encodeAbility(entry, unitID, unitDefID, gameFrame) + local kind = entry.kind + if kind == "reload" then + local _, _, rf = GetUnitWeaponState(unitID, entry.weapon) + return modFrame(rf, gameFrame) + elseif kind == "commReload" then + local wn = GetUnitRulesParam(unitID, "comm_weapon_num_" .. (entry.commWeapon or 1)) + if not wn or wn == 0 then return 0 end + local _, _, rf = GetUnitWeaponState(unitID, wn) + return modFrame(rf, gameFrame) + elseif kind == "scriptReload" then + return modFrame(GetUnitRulesParam(unitID, "scriptReloadFrame"), gameFrame) + elseif kind == "dgun" or kind == "moveDgun" then + local _, _, rf = GetUnitWeaponState(unitID, unitDefDgun[unitDefID]) + return modFrame(rf, gameFrame) + elseif kind == "captureReload" then + return modFrame(GetUnitRulesParam(unitID, "captureRechargeFrame"), gameFrame) + elseif kind == "burst" then + local scriptLoaded = mfloor(GetUnitRulesParam(unitID, "scriptLoaded") or unitDefBurstCount[unitDefID]) + local i = entry.index + if i <= scriptLoaded then return 100 end + if i == scriptLoaded + 1 then + local rf = GetUnitRulesParam(unitID, "scriptReloadFrame") or 0 + if rf <= 0 then return 0 end + local remaining = math.max(0, rf - gameFrame) + return pct100(1.0 - remaining / (unitDefScriptReload[unitDefID] or 1)) + end + return 0 + elseif kind == "shield" then + local on, power = GetUnitShieldState(unitID) + if on == false then power = 0 end + return pct100(1 - (power or 0) / unitDefHasShield[unitDefID]) + elseif kind == "heat" then return pct100(GetUnitRulesParam(unitID, "heat_bar") or 0) + elseif kind == "speed" then return pct100(GetUnitRulesParam(unitID, "speed_bar") or 0) + elseif kind == "teleport" then + -- Frame-based: store the completion frame (teleportend); the badge counts down to it. Not + -- teleporting -> teleportend is -1 -> modFrame returns 0 -> badge hidden. + return modFrame(GetUnitRulesParam(unitID, "teleportend"), gameFrame) + elseif kind == "reammo" then + -- noammo decides VISIBILITY: 1 = out of ammo (flying back), 2 = at the pad -> show the badge; + -- 0/nil = armed, 3 = repairing (ammo done) -> hidden. COUNTING is driven by reammoProgress actually + -- advancing, so it stays frozen while flying back or on a disabled/unpowered pad. + local noammo = GetUnitRulesParam(unitID, "noammo") + if noammo ~= 1 and noammo ~= 2 then reammoRateLast[unitID] = nil; return 0 end -- clear so a re-drop starts fresh + return pausableETABand(reammoRateLast, unitID, GetUnitRulesParam(unitID, "reammoProgress") or 0, + gameFrame, unitDefReammoFrames[unitDefID], true) + elseif kind == "stockProg" then + -- Frame-based rate-ETA to the next missile (stalls when resource-starved). The ready count is + -- the co-located stockpilecount glyph. + local _, _, sb = GetUnitStockpile(unitID) + local ud = UnitDefs[unitDefID] + if ud.customParams and ud.customParams.stockpiletime then sb = GetUnitRulesParam(unitID, "gadgetStockpile") end + return pausableETABand(stockRateLast, unitID, sb or 0, gameFrame) + elseif kind == "stockCnt" then + return mmin(GetUnitStockpile(unitID) or 0, 4095) + elseif kind == "jump" then + local jr = GetUnitRulesParam(unitID, "jumpReload") or 0 + local charges = unitDefHasJump[unitDefID] or 1 + local remaining = charges - jr + if remaining <= 0 then return 0 end -- fully charged sentinel: all badges full (or single hides) + local targetFrame = gameFrame + remaining * (unitDefJumpReloadFrames[unitDefID] or 1) + local v = mfloor(targetFrame / JUMP_FRAME_SCALE) % 4096 + return (v == 0) and 1 or v -- keep 0 reserved for "fully charged" while still recharging + elseif kind == "moveAbility" then + -- Frame-based: specialReloadRemaining is a 0..1 fraction (1 = just used). Convert to a ready-frame + -- now + remaining*duration (specialreloadtime, frames). 0 = ready -> hidden. + local rem = GetUnitRulesParam(unitID, "specialReloadRemaining") or 0 + if rem <= 0 then return 0 end + return modFrame(gameFrame + rem * (tonumber(unitDefHasAbility[unitDefID]) or 1), gameFrame) + elseif kind == "goo" then return pausableETABand(gooRateLast, unitID, GetUnitRulesParam(unitID, "gooState") or 0, gameFrame, unitDefGooFrames[unitDefID]) + end + return 0 +end + +local unitUniform = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} -- channels 1-14 +local writeBuf11 = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} -- block write covers floats 1-11 only: float 12 is gadget cloak (must not clobber), floats 13-14 are unread (their channels map to floats 2/11) +local unitMorphProgress = {} +local unitHighlight = {} -- per-unit highlight/selectedness value (slot 6), pushed via WG.SetUnitHighlight +local unitParalyzeFX = {} -- gfx_paralyze fullscreen-effect value (float 4), pushed via WG.SetUnitParalyzeFX +local unitStateCount = {} -- centered-state count (float 2, bits 19-22) +local unitSelected = {} -- isSelected (float 2, bit 23) +local unitFloat2Base = {} -- slow+capture packed (float 2 low 19 bits), cached so event write-throughs re-pack + +function updateUnit(unitID, unitDefID) + for i = 1, 14 do unitUniform[i] = 0 end + + local health, maxHealth, paralyzeDamage, capture, build = GetUnitHealth(unitID) + paralyzeDamage = GetUnitRulesParam(unitID, "real_para") or paralyzeDamage or 0 + + if (not maxHealth) or (maxHealth < 1) then maxHealth = 1 end + if not build then build = 1 end + + local empHP = (not paralyzeOnMaxHealth) and health or maxHealth + local emp = paralyzeDamage / empHP + + --// BUILD / RECLAIM (ch7): construction progress bar (0..1). buildProgress is < 1 while a nanoframe + -- is being built or a finished unit is being reclaimed (it falls back from 1), and exactly 1 when + -- finished/idle. So < 1 means "under construction or being reclaimed" -> show the fill; 1 -> hidden. + local buildVal = 0 + if build < 1 then + buildVal = math.max(build, 0.02) -- floor so a freshly-placed nanoframe is still visible + end + unitUniform[unitBuildChannel] = buildVal + + --// PARALYZE (ch1) + local stunned = GetUnitIsStunned(unitID) + stunned = stunned and paralyzeDamage >= empHP + if paralyzeDamage >= maxRealPara then + emp = 0 -- spent silo missile sentinel, not real paralyze + elseif stunned then + emp = (paralyzeDamage - empHP) / (maxHealth * empDecline) + 1 + elseif emp > 1 then + emp = 1 + end + unitUniform[unitParalyzeChannel] = emp + + --// CAPTURE (ch4) + unitUniform[unitCaptureChannel] = capture or 0 + + --// DISARM (ch2) + local gameFrame = Spring.GetGameFrame() + local disarmFrame = GetUnitRulesParam(unitID, "disarmframe") + if disarmFrame and disarmFrame ~= -1 and disarmFrame > gameFrame then + local disarmProp = (disarmFrame - gameFrame) / 1200 + if disarmProp < 1 then + if disarmProp > emp + 0.014 then -- 16 gameframes of emp buffer + unitUniform[unitDisarmChannel] = disarmProp + end + else + unitUniform[unitDisarmChannel] = (disarmFrame - gameFrame - 1200) / gameSpeed + 1 + end + end + + --// SLOW (ch3) + -- Below the cap: magnitude bar (the 50% cap reads as a full bar). Above the cap (overslow): + -- value > 1 pins the bar at max and the overflow carries the "locked at max" seconds, for a + -- separate duration badge -- the same single-channel scheme as paralyze/disarm. + local slowState = GetUnitRulesParam(unitID, "slowState") or 0 + if slowState > maxSlowFactor then + unitUniform[unitSlowChannel] = (slowState - maxSlowFactor) / slowDecayPerSecond + 1 + else + unitUniform[unitSlowChannel] = slowState / maxSlowFactor + end + + -- (Ability values are computed in the ABILITY SLOTS walk below via encodeAbility; the old per-ability + -- fixed-channel writes -- primary/burst/secondary reloads, multi-weapon extras, movement, shield, + -- goo/morph -- were removed in favor of the slot model. morph runtime-slot wiring still TODO.) + + --// ABILITY SLOTS: walk the per-unitDef assignment; pack each slot into its float (slots 1,2->float 9; + -- 3,4->float 10; 5->float 11). Sole source of ability slot values (old fixed writes removed). + -- Frees floats 5,8,12,14. + local abSlots = unitDefAbilitySlots[unitDefID] + local s1, s2, s3, s4, s5 = 0, 0, 0, 0, 0 + if abSlots then + if abSlots[1] then s1 = encodeAbility(abSlots[1], unitID, unitDefID, gameFrame) end + if abSlots[2] then s2 = encodeAbility(abSlots[2], unitID, unitDefID, gameFrame) end + if abSlots[3] then s3 = encodeAbility(abSlots[3], unitID, unitDefID, gameFrame) end + if abSlots[4] then s4 = encodeAbility(abSlots[4], unitID, unitDefID, gameFrame) end + if abSlots[5] then s5 = encodeAbility(abSlots[5], unitID, unitDefID, gameFrame) end + end + unitUniform[9] = s1 + s2 * 4096 + unitUniform[10] = s3 + s4 * 4096 + unitUniform[11] = s5 + unitUniform[5] = 0 + unitUniform[8] = pausableETABand(morphRateLast, unitID, unitMorphProgress[unitID] or 0, gameFrame) -- morph pausable-ETA band on float 8 + -- float 12 = gadget cloak (not written here), float 14 unread (channel 14 -> float 11): both outside the 1-11 block write + + -- Pack paralyze+disarm into float 1, slow+capture into float 2 (frees floats 3, 13). + -- (shield is an ability -> handled in the ability-slot step; morph is pushed to float 8 above.) + local slowEnc = encodeStatus(unitUniform[unitSlowChannel], gameFrame) + local captureEnc = mfloor(unitUniform[unitCaptureChannel] * 100 + 0.5) + unitUniform[1] = encodeStatus(unitUniform[1], gameFrame) + encodeStatus(unitUniform[2], gameFrame) * 4096 + local f2base = slowEnc + captureEnc * 4096 + unitFloat2Base[unitID] = f2base -- so writeStateFlags can re-pack float 2 on state/selection events + unitUniform[2] = f2base + (unitStateCount[unitID] or 0) * 524288 + (unitSelected[unitID] and 8388608 or 0) + unitUniform[unitSlowChannel] = 0 -- float 3 freed + unitUniform[unitCaptureChannel] = 0 -- float 13 freed + -- Fold in the externally-pushed highlight (slot 6) and gfx_paralyze effect value (float 4) so the + -- block write preserves them instead of zeroing those slots. + unitUniform[unitSelectednessChannel] = unitHighlight[unitID] or 0 + unitUniform[4] = unitParalyzeFX[unitID] or 0 + for i = 1, 11 do writeBuf11[i] = unitUniform[i] end + glSetUnitBufferUniforms(unitID, writeBuf11, 1) -- floats 1-11 only; float 12 (cloak) and 13-15 left to the gadget / unread +end + +function updateUnits() + local nextBlock = currentUnit + unitUpdateRate - 1 + if nextBlock > unitsCount then + nextBlock = unitsCount + end + for i = currentUnit, nextBlock do + local unitID = units[i] + if Spring.ValidUnitID(unitID) then + updateUnit(unitID, GetUnitDefID(unitID)) + end + end + currentUnit = nextBlock + 1 + if currentUnit > unitsCount then + currentUnit = 1 + end +end + +function addUnit(unitID, unitDefID) + if unitPosition[unitID] ~= nil then return end + unitsCount = unitsCount + 1 + units[unitsCount] = unitID + unitPosition[unitID] = unitsCount + updateUnit(unitID, unitDefID) +end + +function removeUnit(unitID) + local position = unitPosition[unitID] + if position == nil then return end + local lastUnit = units[unitsCount] + units[position] = lastUnit + unitPosition[lastUnit] = position + units[unitsCount] = nil + unitPosition[unitID] = nil + unitsCount = unitsCount - 1 + unitHighlight[unitID] = nil + unitStateCount[unitID] = nil + unitSelected[unitID] = nil + unitFloat2Base[unitID] = nil + unitParalyzeFX[unitID] = nil +end + +function resetUnits() + units = {} + unitsCount = 0 + unitPosition = {} + currentUnit = 1 + unitMorphProgress = {} + unitHighlight = {} + unitStateCount = {} + unitSelected = {} + unitFloat2Base = {} + unitParalyzeFX = {} + + local spec, fullview = Spring.GetSpectatingState() + local allUnits = Spring.GetAllUnits() + for i = 1, #allUnits do + local unitID = allUnits[i] + if fullview or Spring.GetUnitLosState(unitID, myAllyTeamID).los then + addUnit(unitID, GetUnitDefID(unitID)) + end + end +end + +function widget:VisibleUnitAdded(unitID, unitDefID, unitTeam) + addUnit(unitID, unitDefID) +end + +function widget:VisibleUnitRemoved(unitID) + removeUnit(unitID) +end + +function widget:PlayerChanged(playerID) + myAllyTeamID = Spring.GetMyAllyTeamID() + resetUnits() +end + +function widget:VisibleUnitsChanged(visibleUnits, numVisibleUnits) + resetUnits() +end + +function initUnits() + resetUnits() +end + +----------------------------------------------------------------- +-- Features +----------------------------------------------------------------- + +local GetVisibleFeatures = Spring.GetVisibleFeatures +local GetFeatureDefID = Spring.GetFeatureDefID +local GetFeatureHealth = Spring.GetFeatureHealth +local GetFeatureResources = Spring.GetFeatureResources +local glSetFeatureBufferUniforms = gl.SetFeatureBufferUniforms + +local trackedFeatures = {} +for i = 1, #FeatureDefs do + trackedFeatures[i] = FeatureDefs[i].destructable and FeatureDefs[i].drawTypeString == "model" +end + +local features = {} +local featureUpdateRate = 200.0 + +local featureUniform = {0, 0, 0} +function updateFeature(featureID) + local health, maxHealth, resurrect = GetFeatureHealth(featureID) + local _, _, _, _, reclaim = GetFeatureResources(featureID) + featureUniform[featureHealthChannel] = (health or 0) / (maxHealth or 1) + + -- Resurrect ("raise") progress bar (0..1) as the wreck is raised; 0 = not being raised -> hidden. + resurrect = resurrect or 0 + featureUniform[featureResurrectChannel] = (resurrect > 0) and math.max(resurrect, 0.02) or 0 + featureUniform[featureReclaimChannel] = reclaim or 0 + glSetFeatureBufferUniforms(featureID, featureUniform, 1) +end + +function addFeature(featureID, defID) + features[featureID] = defID + updateFeature(featureID) + for _, callback in pairs(WG.GlUnionUpdaterAddFeatureCallbacks) do + callback(featureID) + end +end + +function removeFeature(featureID) + features[featureID] = nil + for _, callback in pairs(WG.GlUnionUpdaterRemoveFeatureCallbacks) do + callback(featureID) + end +end + +function updateFeatures() + local visibleFeatures = GetVisibleFeatures(-1, nil, false, false) + local removedFeatures = {} + + local updatePercent = ceil(#visibleFeatures / featureUpdateRate) + for featureID, _ in pairs(features) do + removedFeatures[featureID] = true + end + + local cnt = #visibleFeatures + for i = 1, cnt do + local featureID = visibleFeatures[i] + local featureDefID = GetFeatureDefID(featureID) or -1 + if trackedFeatures[featureDefID] then + if removedFeatures[featureID] then + if updatePercent < 2 or (updateCount + featureID) % updatePercent == 0 then + updateFeature(featureID) + end + removedFeatures[featureID] = nil + else + addFeature(featureID, featureDefID) + end + end + end + + for featureID, val in pairs(removedFeatures) do + if val then + removeFeature(featureID) + end + end +end + +----------------------------------------------------------------- +-- Highlight (selectedness, slot unitSelectednessChannel) +-- The updater owns the GL write so it stays the sole writer of the channel; callers express intent +-- (highlight this unit/feature) and never see the slot number. Keeps highlight from colliding with +-- the per-frame block write that previously zeroed slot 6. +----------------------------------------------------------------- + +local highlightCache = {0} + +local function setUnitHighlight(unitID, value) + value = value or 0 + unitHighlight[unitID] = (value ~= 0) and value or nil + -- Write through now so a hover is instant; updateUnit folds the stored value into later passes. + if Spring.ValidUnitID(unitID) then + highlightCache[1] = value + glSetUnitBufferUniforms(unitID, highlightCache, unitSelectednessChannel) + end +end + +local function setFeatureHighlight(featureID, value) + -- No store needed: the feature block write only covers slots 1-3, so slot 6 is never clobbered. + if Spring.ValidFeatureID(featureID) then + highlightCache[1] = value or 0 + glSetFeatureBufferUniforms(featureID, highlightCache, unitSelectednessChannel) + end +end + +-- gfx_paralyze pushes its fullscreen-effect value here (instead of writing float 4 directly); stored and +-- folded into the block write so it isn't clobbered. gfx keeps its own linger/draw-list state machine. +local function setUnitParalyzeFX(unitID, value) + value = value or 0 + unitParalyzeFX[unitID] = (value ~= 0) and value or nil + if Spring.ValidUnitID(unitID) then + highlightCache[1] = value + glSetUnitBufferUniforms(unitID, highlightCache, 4) -- write-through; updateUnit re-folds it each pass + end +end + +-- Float 2 packs slow+capture (low 19 bits, round-robin) + state-count (bits 19-22) + isSelected (bit 23), +-- both event-driven. The round-robin block write folds in the stores; these event write-throughs re-pack +-- from the cached slow+capture base (unitFloat2Base) + the stores. (float 15 is now free.) +local stateFlagsCache = {0} + +local function writeStateFlags(unitID) + if Spring.ValidUnitID(unitID) then + -- Re-pack float 2 from the cached slow+capture base + the event-driven state-count/isSelected. + stateFlagsCache[1] = (unitFloat2Base[unitID] or 0) + (unitStateCount[unitID] or 0) * 524288 + (unitSelected[unitID] and 8388608 or 0) + glSetUnitBufferUniforms(unitID, stateFlagsCache, 2) + end +end + +local function setUnitStateCount(unitID, count) + count = count or 0 + if count > 15 then count = 15 end -- 4-bit field + unitStateCount[unitID] = (count ~= 0) and count or nil + writeStateFlags(unitID) +end + +local function setUnitSelected(unitID, selected) + unitSelected[unitID] = selected and true or nil + writeStateFlags(unitID) +end + +----------------------------------------------------------------- +-- Widget +----------------------------------------------------------------- + +local selectedSet = {} +function widget:SelectionChanged(selectedUnits) + local newSet = {} + for i = 1, #selectedUnits do newSet[selectedUnits[i]] = true end + for unitID in pairs(selectedSet) do + if not newSet[unitID] then setUnitSelected(unitID, false) end + end + for unitID in pairs(newSet) do + if not selectedSet[unitID] then setUnitSelected(unitID, true) end + end + selectedSet = newSet +end + +function widget:Update() + updateCount = updateCount + 1 + updateUnits() + updateFeatures() +end + +function widget:Initialize() + WG.GlUnionUpdaterAddFeatureCallbacks = WG.GlUnionUpdaterAddFeatureCallbacks or {} + WG.GlUnionUpdaterRemoveFeatureCallbacks = WG.GlUnionUpdaterRemoveFeatureCallbacks or {} + + WG.SetUnitHighlight = setUnitHighlight + WG.SetFeatureHighlight = setFeatureHighlight + WG.SetUnitStateCount = setUnitStateCount + WG.SetUnitSelected = setUnitSelected + WG.SetUnitParalyzeFX = setUnitParalyzeFX + + WG.MorphUpdateCallbacks = WG.MorphUpdateCallbacks or {} + WG.MorphStartCallbacks = WG.MorphStartCallbacks or {} + WG.MorphStopCallbacks = WG.MorphStopCallbacks or {} + + local widgetName = widget:GetInfo().name + WG.MorphUpdateCallbacks[widgetName] = function(morphTable) + for unitID, morph in pairs(morphTable) do + unitMorphProgress[unitID] = morph.progress + end + end + WG.MorphStopCallbacks[widgetName] = function(unitID) + unitMorphProgress[unitID] = nil + end + + initUnits() +end + +function widget:Shutdown() + local widgetName = widget:GetInfo().name + WG.MorphUpdateCallbacks[widgetName] = nil + WG.MorphStopCallbacks[widgetName] = nil +end diff --git a/LuaUI/Widgets/unit_healthbars.lua b/LuaUI/Widgets/unit_healthbars.lua index fae719b9d7..1a8ed2fd89 100644 --- a/LuaUI/Widgets/unit_healthbars.lua +++ b/LuaUI/Widgets/unit_healthbars.lua @@ -17,8 +17,8 @@ function widget:GetInfo() author = "jK", date = "2009", --2013 May 12 license = "GNU GPL, v2 or later", - layer = -11, -- above gui_selectedunits_gl4, below gui_name_tags - enabled = true -- loaded by default? + layer = -11, -- above gui_selectedunits_gl4, below gui_name_tags + enabled = false -- loaded by default? } end diff --git a/LuaUI/Widgets/unit_icons.lua b/LuaUI/Widgets/unit_icons.lua index b428c916ed..a7c182a86d 100644 --- a/LuaUI/Widgets/unit_icons.lua +++ b/LuaUI/Widgets/unit_icons.lua @@ -1,385 +1,29 @@ ------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------- +-- RETIRED. Hovering unit-state icons are now rendered by the "Unit Overlay GL4" widget +-- (gui_unit_overlay_gl4.lua), which owns the WG.icons backend (instanced, unified row layout). +-- +-- This file used to be a second WG.icons backend: it claimed `WG.icons`, stored the provider +-- data in its own tables, and drew the icons immediate-mode at full unit height. With the GL4 +-- overlay also providing WG.icons, the two fought over the global depending on load order -- when +-- this widget won, the overlay never received the provider data (its row stayed empty) and the +-- icons were drawn here at the wrong (full) height instead of in the overlay's row. +-- +-- It is now an inert stub: it does NOT touch WG.icons and draws nothing, so the overlay is the +-- sole owner regardless of load order and the provider widgets (unit_state_icons, unit_rank_icons, +-- api_gadget_icons, unit_global_build_command) feed the overlay directly. Kept (rather than deleted) +-- only so existing "enabled" widget configs load cleanly; safe to leave enabled or disabled. +------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------- function widget:GetInfo() return { name = "Unit Icons", - desc = "v0.033 Shows icons above units", + desc = "Retired: hovering state icons are rendered by Unit Overlay GL4 (which backs WG.icons). Inert stub, draws nothing.", author = "CarRepairer and GoogleFrog", date = "2012-01-28", license = "GNU GPL, v2 or later", - layer = -13, -- above gui_name_tags - enabled = true, -- loaded by default? + layer = -13, + enabled = false, } end - -------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------- - -local echo = Spring.Echo - -local spGetUnitDefID = Spring.GetUnitDefID -local spIsUnitInView = Spring.IsUnitInView -local spGetUnitViewPosition = Spring.GetUnitViewPosition -local spGetGameFrame = Spring.GetGameFrame - -local glDepthTest = gl.DepthTest -local glDepthMask = gl.DepthMask -local glAlphaTest = gl.AlphaTest -local glTexture = gl.Texture -local glTexRect = gl.TexRect -local glTranslate = gl.Translate -local glBillboard = gl.Billboard -local glDrawFuncAtUnit = gl.DrawFuncAtUnit -local glPushMatrix = gl.PushMatrix -local glPopMatrix = gl.PopMatrix - -local GL_GREATER = GL.GREATER - -local min = math.min -local max = math.max -local floor = math.floor -local abs = math.abs - -local iconsize = 5 -local forRadarIcons = true - ----------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------- - -options_path = 'Settings/Interface/Hovering Icons' -options = { - iconsize = { - name = 'Hovering Icon Size', - type = 'number', - value = 30, min=10, max = 40, - OnChange = function(self) - iconsize = self.value - end - }, - forRadarIcons = { - name = 'Draw on Icons', - desc = 'Draws state icons when zoomed out.', - type = 'bool', - value = true, - noHotkey = true, - OnChange = function(self) - forRadarIcons = self.value - end - }, -} -options.iconsize.OnChange(options.iconsize) - ----------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------- - -local unitHeights = {} -local iconOrders = {} -local iconOrders_order = {} - -local iconoffset = 26 - - -local iconUnitTexture = {} ---local textureUnitsXshift = {} -local textureData = {} - -local textureIcons = {} -local textureOrdered = {} - -local textureColors = {} - ---local xshiftUnitTexture = {} - -local hideIcons = {} -local pulseIcons = {} - -local updateTime, iconFade = 0, 0 - -WG.icons = {} - -------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------- - -local function OrderIcons() - iconOrders_order = {} - for iconName, _ in pairs(iconOrders) do - iconOrders_order[#iconOrders_order+1] = iconName - end - table.sort(iconOrders_order, function(a,b) - return iconOrders[ a ] < iconOrders[ b ] - end) - -end - -local function OrderIconsOnUnit(unitID ) - local iconCount = 0 - for i=1, #iconOrders_order do - local iconName = iconOrders_order[i] - if (not hideIcons[iconName]) and iconUnitTexture[iconName] and iconUnitTexture[iconName][unitID] then - iconCount = iconCount + 1 - end - end - local xshift = (0.5 - iconCount*0.5)*iconsize - - for i=1, #iconOrders_order do - local iconName = iconOrders_order[i] - local texture = iconUnitTexture[iconName] and iconUnitTexture[iconName][unitID] - if texture then - - if hideIcons[iconName] then - --textureUnitsXshift[texture][unitID] = nil - textureData[texture][iconName][unitID] = nil - else - --textureUnitsXshift[texture][unitID] = xshift - textureData[texture][iconName][unitID] = xshift - xshift = xshift + iconsize - end - end - - end -end - -local function SetOrder(iconName, order) - iconOrders[iconName] = order - OrderIcons() -end - - -local function ReOrderIconsOnUnits() - local units = Spring.GetAllUnits() - for i=1,#units do - OrderIconsOnUnit(units[i]) - end -end - - -function WG.icons.SetDisplay( iconName, show ) - local hide = (not show) or nil - curHide = hideIcons[iconName] - if curHide ~= hide then - hideIcons[iconName] = hide - ReOrderIconsOnUnits() - end -end - -function WG.icons.SetOrder( iconName, order ) - SetOrder(iconName, order) -end - -function WG.icons.SetPulse( iconName, pulse ) - pulseIcons[iconName] = pulse -end - - -function WG.icons.SetUnitIcon(unitID, data) - local iconName = data.name - local texture = data.texture - local color = data.color - - if not iconOrders[iconName] then - SetOrder(iconName, math.huge) - end - - - local oldTexture = iconUnitTexture[iconName] and iconUnitTexture[iconName][unitID] - if oldTexture then - --textureUnitsXshift[oldTexture][unitID] = nil - textureData[oldTexture][iconName][unitID] = nil - - iconUnitTexture[iconName][unitID] = nil - if (not hideIcons[iconName])then - OrderIconsOnUnit(unitID) - end - end - if not texture then - return - end - - --[[ - if not textureUnitsXshift[texture] then - textureUnitsXshift[texture] = {} - end - textureUnitsXshift[texture][unitID] = 0 - --]] - - --new - if not textureData[texture] then - textureData[texture] = {} - end - if not textureData[texture][iconName] then - textureData[texture][iconName] = {} - end - textureData[texture][iconName][unitID] = 0 - - if color then - if not textureColors[unitID] then - textureColors[unitID] = {} - end - textureColors[unitID][iconName] = color - end - - if not iconUnitTexture[iconName] then - iconUnitTexture[iconName] = {} - end - iconUnitTexture[iconName][unitID] = texture - - if not unitHeights[unitID] then - local ud = UnitDefs[spGetUnitDefID(unitID)] - if (ud == nil) then - unitHeights[unitID] = nil - else - --unitHeights[unitID] = Spring.Utilities.GetUnitHeight(ud) + iconoffset - unitHeights[unitID] = Spring.GetUnitHeight(unitID) + iconoffset + (tonumber(ud.customParams.health_bar_height) or 0) - end - end - - OrderIconsOnUnit(unitID) -end - -------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------- - -function widget:UnitDestroyed(unitID, unitDefID, unitTeam) - unitHeights[unitID] = nil - --xshiftUnitTexture[unitID] = nil -end - -------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------- - -local function DrawFuncAtUnitIcon2(unitID, xshift, yshift) - local heightMult = Spring.GetUnitRulesParam(unitID, "currentModelScale") or 1 - local x,y,z = spGetUnitViewPosition(unitID) - glPushMatrix() - glTranslate(x,y,z) - glTranslate(0,yshift*heightMult,0) - glBillboard() - glTexRect(xshift -iconsize*0.5, -5, xshift + iconsize*0.5, iconsize-5) - glPopMatrix() -end - -local function DrawUnitFunc(unitID, xshift, yshift) - local heightMult = Spring.GetUnitRulesParam(unitID, "currentModelScale") or 1 - glTranslate(0,yshift*heightMult,0) - glBillboard() - glTexRect(xshift - iconsize*0.5, -9, xshift + iconsize*0.5, iconsize-9) -end - -local function DrawWorldFunc() - if Spring.IsGUIHidden() then - return - end - - if (next(unitHeights) == nil) then - return -- avoid unnecessary GL calls - end - - local gameFrame = spGetGameFrame() - - gl.Color(1,1,1,1) - glDepthMask(true) - glDepthTest(true) - glAlphaTest(GL_GREATER, 0.001) - - --for texture, units in pairs(textureUnitsXshift) do - for texture, curTextureData in pairs(textureData) do - for iconName, units in pairs(curTextureData) do - glTexture(texture) - for unitID,xshift in pairs(units) do - local textureColor = textureColors[unitID] and textureColors[unitID][iconName] - if textureColor then - gl.Color( textureColor ) - elseif pulseIcons[iconName] then - gl.Color( 1,1,1,iconFade ) - end - - local unitInView = spIsUnitInView(unitID) - if unitInView and xshift and unitHeights and unitHeights[unitID] then - if forRadarIcons then - DrawFuncAtUnitIcon2(unitID, xshift, unitHeights[unitID]) - else - glDrawFuncAtUnit(unitID, false, DrawUnitFunc, unitID, xshift,unitHeights[unitID]) - end - end - - if textureColor or pulseIcons[iconName] then - gl.Color(1,1,1,1) - end - end - end - end - - glTexture(false) - - glAlphaTest(false) - glDepthTest(false) - glDepthMask(false) -end - -function widget:DrawWorld() - DrawWorldFunc() -end - -function widget:DrawWorldRefraction() - DrawWorldFunc() -end - -function widget:Update(dt) - updateTime = (updateTime + dt)%2 - iconFade = min(abs(((updateTime*30) % 60) - 20) / 20, 1 ) -end - --- drawscreen method --- the problem with this one is it draws at same size regardless of how far away the unit is ---[[ -function widget:DrawScreenEffects() - if Spring.IsGUIHidden() then return end - - if (next(unitHeights) == nil) then - return -- avoid unnecessary GL calls - end - - gl.Color(1,1,1,1) - glDepthMask(true) - glDepthTest(true) - glAlphaTest(GL_GREATER, 0.001) - - for texture, units in pairs(textureUnitsXshift) do - glTexture( texture ) - for unitID,xshift in pairs(units) do - gl.PushMatrix() - local x,y,z = Spring.GetUnitPosition(unitID) - y = y + (unitHeights[unitID] or 0) - x,y,z = Spring.WorldToScreenCoords(x,y,z) - glTranslate(x,y,z) - if xshift and unitHeights then - glTranslate(xshift,0,0) - --glBillboard() - glTexRect(-iconsize*0.5, -9, iconsize*0.5, iconsize-9) - end - gl.PopMatrix() - end - end - - glTexture(false) - - glAlphaTest(false) - glDepthTest(false) - glDepthMask(false) -end - -]] - -function widget:Initialize() -end - -function widget:Shutdown() - --for texture,_ in pairs(textureUnitsXshift) do - for texture,_ in pairs(textureData) do - gl.DeleteTexture(texture) - end -end - -------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------- diff --git a/LuaUI/Widgets/unit_state_icons.lua b/LuaUI/Widgets/unit_state_icons.lua index bce0d26828..dae618da10 100644 --- a/LuaUI/Widgets/unit_state_icons.lua +++ b/LuaUI/Widgets/unit_state_icons.lua @@ -34,66 +34,9 @@ local floor = math.floor ---------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------- -options_path = 'Settings/Interface/Hovering Icons' -options = { - - showstateonshift = { - name = "Show move/fire states on shift", - desc = "When holding shift, icons appear over units indicating move state and fire state.", - type = 'bool', - value = false, - noHotkey = true, - }, - showarmorstate = { - name = 'Armor state visibility', - desc = "When to show an icon for armored units.", - type = 'radioButton', - value = 'shift', - items = { - {key ='always', name='Always'}, - {key ='shift', name='When holding Shift'}, - {key ='never', name='Never'}, - -- an option to show armor on enemies would be good. Gadget assumes units are own so would need some rewriting. - }, - OnChange = function (this) - if this.value == 'always' then - WG.icons.SetDisplay('armored', true) - else - WG.icons.SetDisplay('armored', false) - end - end, - noHotkey = true, - }, - - showpriority = { - name = "Priority state visibility", - desc = "When to show an icon for prioritized units.", - type = 'radioButton', - value = 'shift', - items = { - {key ='always', name='Always'}, - {key ='shift', name='When holding Shift'}, - {key ='never', name='Never'}, - }, - OnChange = function (this) - if this.value == 'always' then - WG.icons.SetDisplay('priority', true) - else - WG.icons.SetDisplay('priority', false) - end - end, - noHotkey = true, - }, - showmiscpriorityonshift = { - name = "Show misc priorty on shift", - desc = "When holding shift, an icon appears over unit with low or high misc priority (morph or stockpile).", - type = 'bool', - value = true, - noHotkey = true, - }, -} - -include("keysym.lua") +-- Visibility of these state icons is now owned by the Unit Overlay GL4 widget's "Unit States" +-- options (the single source of truth). This widget only computes the current state and pushes +-- the icon data through WG.icons; the overlay decides what is shown via WG.icons.SetDisplay. local imageDir = 'LuaUI/Images/commands/' local fireStateIcons = { @@ -119,8 +62,6 @@ local miscPriorityIcons = { local armoredTexture = 'Luaui/Images/commands/guard.png' -local hide = true - ------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------- @@ -141,7 +82,7 @@ function SetUnitStateIcons(unitID) return end - if options.showstateonshift.value then + do local firestate, movestate if REVERSE_COMPAT then local states = spGetUnitStates(unitID) @@ -167,7 +108,7 @@ function SetUnitStateIcons(unitID) end end - if options.showarmorstate.value ~= "never" then + do local armored, amount = spGetUnitArmored(unitID) armored = armored and amount and amount ~= 1 if not lastArmored[unitID] and armored then @@ -179,7 +120,7 @@ function SetUnitStateIcons(unitID) end end - if options.showpriority.value ~= "never" then + do local state = spGetUnitRulesParam(unitID, "buildpriority") if (not ud) or not (ud.canAssist and ud.buildSpeed ~= 0) then local _,_,_,_,buildProgress = spGetUnitHealth(unitID) @@ -187,7 +128,7 @@ function SetUnitStateIcons(unitID) state = 1 end end - + if not prevPriority[unitID] or prevPriority[unitID] ~= state then if state == 1 then prevPriority[unitID] = state @@ -199,10 +140,10 @@ function SetUnitStateIcons(unitID) end end end - - if options.showmiscpriorityonshift.value then + + do local state = spGetUnitRulesParam(unitID, "miscpriority") - + if not prevMiscPriority[unitID] or prevMiscPriority[unitID] ~= state then if state == 1 then prevMiscPriority[unitID] = state @@ -217,9 +158,6 @@ function SetUnitStateIcons(unitID) end local function UpdateAllUnits() - if hide and not ((options.showpriority.value == "always") or (options.showarmorstate.value == "always")) then - return - end local unitID local units = spGetAllUnits() for i = 1, #units do @@ -245,50 +183,6 @@ function widget:UnitDestroyed(unitID, unitDefID, unitTeam) WG.icons.SetUnitIcon( unitID, {name='miscpriority', texture=nil} ) end -function widget:KeyPress(key, modifier, isRepeat) - if isRepeat then - return - end - - if key == KEYSYMS.LSHIFT or key == KEYSYMS.RSHIFT then - hide = false - - if options.showstateonshift.value then - WG.icons.SetDisplay('firestate', true) - WG.icons.SetDisplay('movestate', true) - end - if options.showarmorstate.value == "shift" then - WG.icons.SetDisplay('armored', true) - end - if options.showpriority.value == "shift" then - WG.icons.SetDisplay('priority', true) - end - if options.showmiscpriorityonshift.value then - WG.icons.SetDisplay('miscpriority', true) - end - - UpdateAllUnits() - end -end -function widget:KeyRelease(key, modifier ) - if key == KEYSYMS.LSHIFT or key == KEYSYMS.RSHIFT then - hide = true - - WG.icons.SetDisplay('firestate', false) - WG.icons.SetDisplay('movestate', false) - - if options.showarmorstate.value == "shift" then - WG.icons.SetDisplay('armored', false) - end - if options.showpriority.value == "shift" then - WG.icons.SetDisplay('priority', false) - end - - WG.icons.SetDisplay('miscpriority', false) - end -end - - -- todo: intercept state change and (un-)armoring events, and get rid of polling altogether function widget:GameFrame(f) @@ -299,18 +193,16 @@ end function widget:UnitCommand(unitID, unitDefID, unitTeam, cmdID, cmdParams, cmdOptions) -- this won't handle priority for some reason, so that should be intercepted right in the widgets giving the order - if (not hide) and ((cmdID == CMD.MOVE_STATE) or (cmdID == CMD.FIRE_STATE)) then + if (cmdID == CMD.MOVE_STATE) or (cmdID == CMD.FIRE_STATE) then SetUnitStateIcons(unitID) end end function widget:Initialize() - + WG.icons.SetOrder( 'firestate', 5 ) WG.icons.SetOrder( 'movestate', 6 ) - WG.icons.SetDisplay('firestate', false) - WG.icons.SetDisplay('movestate', false) - + UpdateAllUnits() end diff --git a/ModelMaterials_GL4/templates/cus_gl4.vert.glsl b/ModelMaterials_GL4/templates/cus_gl4.vert.glsl index 1663809cc6..276d912a1c 100644 --- a/ModelMaterials_GL4/templates/cus_gl4.vert.glsl +++ b/ModelMaterials_GL4/templates/cus_gl4.vert.glsl @@ -582,7 +582,7 @@ void main(void) //worldPos.x += 64; // for dem debuggins - pieceVertexPosOrig.w = modelPos.y / (max(1.0, UNITUNIFORMS.userDefined[2].w)); // 11 is unit height + pieceVertexPosOrig.w = modelPos.y / (max(1.0, UNITUNIFORMS.userDefined[3].w)); // float 15 is unit height (moved off 11) //gl_TexCoord[0] = gl_MultiTexCoord0; uint teamIndex = (instData.z & 0x000000FFu); //leftmost ubyte is teamIndex diff --git a/gamedata/modularcomms/weapons/aalaser.lua b/gamedata/modularcomms/weapons/aalaser.lua index 80c2c66635..e28f44917f 100644 --- a/gamedata/modularcomms/weapons/aalaser.lua +++ b/gamedata/modularcomms/weapons/aalaser.lua @@ -12,6 +12,8 @@ local weaponDef = { cylinderTargeting = 1, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], is_unit_weapon = 1, slot = [[5]], diff --git a/gamedata/modularcomms/weapons/aamissile.lua b/gamedata/modularcomms/weapons/aamissile.lua index 67351bb9f1..972fcbbeb5 100644 --- a/gamedata/modularcomms/weapons/aamissile.lua +++ b/gamedata/modularcomms/weapons/aamissile.lua @@ -9,6 +9,8 @@ local weaponDef = { cylinderTargeting = 1, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], is_unit_weapon = 1, slot = [[5]], muzzleEffectFire = [[custom:CRASHMUZZLE]], diff --git a/gamedata/modularcomms/weapons/assaultcannon.lua b/gamedata/modularcomms/weapons/assaultcannon.lua index b692b8a6b5..7e511f0b22 100644 --- a/gamedata/modularcomms/weapons/assaultcannon.lua +++ b/gamedata/modularcomms/weapons/assaultcannon.lua @@ -7,6 +7,8 @@ local weaponDef = { craterMult = 3, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_assaultcannon.png]], is_unit_weapon = 1, slot = [[5]], muzzleEffectFire = [[custom:RAIDMUZZLE]], diff --git a/gamedata/modularcomms/weapons/beamlaser.lua b/gamedata/modularcomms/weapons/beamlaser.lua index 792f2b22e9..652847b60d 100644 --- a/gamedata/modularcomms/weapons/beamlaser.lua +++ b/gamedata/modularcomms/weapons/beamlaser.lua @@ -8,6 +8,8 @@ local weaponDef = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], is_unit_weapon = 1, slot = [[5]], muzzleEffectShot = [[custom:BEAMWEAPON_MUZZLE_BLUE]], diff --git a/gamedata/modularcomms/weapons/clusterbomb.lua b/gamedata/modularcomms/weapons/clusterbomb.lua index 5d07fd42f9..78d746a124 100644 --- a/gamedata/modularcomms/weapons/clusterbomb.lua +++ b/gamedata/modularcomms/weapons/clusterbomb.lua @@ -12,6 +12,8 @@ local weaponDef = { craterMult = 2, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_clusterbomb.png]], is_unit_weapon = 1, slot = [[3]], muzzleEffectFire = [[custom:HEAVY_CANNON_MUZZLE]], diff --git a/gamedata/modularcomms/weapons/concussion.lua b/gamedata/modularcomms/weapons/concussion.lua index e584dde2f1..8f3cc6542a 100644 --- a/gamedata/modularcomms/weapons/concussion.lua +++ b/gamedata/modularcomms/weapons/concussion.lua @@ -9,6 +9,8 @@ local weaponDef = { craterMult = 2, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_concussion.png]], is_unit_weapon = 1, slot = [[3]], muzzleEffectFire = [[custom:RAIDMUZZLE]], diff --git a/gamedata/modularcomms/weapons/disintegrator.lua b/gamedata/modularcomms/weapons/disintegrator.lua index e671c8daab..14f6fad06c 100644 --- a/gamedata/modularcomms/weapons/disintegrator.lua +++ b/gamedata/modularcomms/weapons/disintegrator.lua @@ -11,6 +11,8 @@ local weaponDef = { craterMult = 6, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_disintegrator.png]], is_unit_weapon = 1, muzzleEffectShot = [[custom:ataalaser]], slot = [[3]], diff --git a/gamedata/modularcomms/weapons/disruptor.lua b/gamedata/modularcomms/weapons/disruptor.lua index d69dc8b395..ee0aa3a985 100644 --- a/gamedata/modularcomms/weapons/disruptor.lua +++ b/gamedata/modularcomms/weapons/disruptor.lua @@ -9,6 +9,8 @@ local weaponDef = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_slowbeam.png]], is_unit_weapon = 1, timeslow_damagefactor = [[2]], diff --git a/gamedata/modularcomms/weapons/disruptorbomb.lua b/gamedata/modularcomms/weapons/disruptorbomb.lua index 168dc3c3d8..993949c2cd 100644 --- a/gamedata/modularcomms/weapons/disruptorbomb.lua +++ b/gamedata/modularcomms/weapons/disruptorbomb.lua @@ -9,6 +9,8 @@ local weaponDef = { craterMult = 0, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_disruptorbomb.png]], is_unit_weapon = 1, slot = [[3]], timeslow_damagefactor = [[10]], diff --git a/gamedata/modularcomms/weapons/flakcannon.lua b/gamedata/modularcomms/weapons/flakcannon.lua index bd284f4575..f0a23c461e 100644 --- a/gamedata/modularcomms/weapons/flakcannon.lua +++ b/gamedata/modularcomms/weapons/flakcannon.lua @@ -11,6 +11,8 @@ local weaponDef = { cylinderTargeting = 1, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_missilelauncher.png]], is_unit_weapon = 1, slot = [[5]], muzzleEffectFire = [[custom:HEAVY_CANNON_MUZZLE]], diff --git a/gamedata/modularcomms/weapons/flamethrower.lua b/gamedata/modularcomms/weapons/flamethrower.lua index 0059c30910..13472d92c6 100644 --- a/gamedata/modularcomms/weapons/flamethrower.lua +++ b/gamedata/modularcomms/weapons/flamethrower.lua @@ -11,6 +11,8 @@ local weaponDef = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_flamethrower.png]], is_unit_weapon = 1, slot = [[5]], muzzleEffectFire = [[custom:RAIDMUZZLE]], diff --git a/gamedata/modularcomms/weapons/gaussrifle.lua b/gamedata/modularcomms/weapons/gaussrifle.lua index d99f320ebc..32c0f9f1d8 100644 --- a/gamedata/modularcomms/weapons/gaussrifle.lua +++ b/gamedata/modularcomms/weapons/gaussrifle.lua @@ -11,6 +11,8 @@ local weaponDef = { craterMult = 0, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], is_unit_weapon = 1, slot = [[5]], muzzleEffectFire = [[custom:flashmuzzle1]], diff --git a/gamedata/modularcomms/weapons/heatray.lua b/gamedata/modularcomms/weapons/heatray.lua index 283fbfdfdb..169556fe80 100644 --- a/gamedata/modularcomms/weapons/heatray.lua +++ b/gamedata/modularcomms/weapons/heatray.lua @@ -8,6 +8,8 @@ local weaponDef = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_heatray.png]], is_unit_weapon = 1, slot = [[5]], diff --git a/gamedata/modularcomms/weapons/heavy_disruptor.lua b/gamedata/modularcomms/weapons/heavy_disruptor.lua index 75f368dd70..722e5b7fd6 100644 --- a/gamedata/modularcomms/weapons/heavy_disruptor.lua +++ b/gamedata/modularcomms/weapons/heavy_disruptor.lua @@ -9,6 +9,8 @@ local weaponDef = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_slowbeam.png]], is_unit_weapon = 1, timeslow_damagefactor = [[2]], diff --git a/gamedata/modularcomms/weapons/heavymachinegun.lua b/gamedata/modularcomms/weapons/heavymachinegun.lua index b005e84539..ac0819f964 100644 --- a/gamedata/modularcomms/weapons/heavymachinegun.lua +++ b/gamedata/modularcomms/weapons/heavymachinegun.lua @@ -10,6 +10,8 @@ local weaponDef = { craterMult = 0.3, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_heavymachinegun.png]], is_unit_weapon = 1, slot = [[5]], muzzleEffectShot = [[custom:WARMUZZLE]], diff --git a/gamedata/modularcomms/weapons/heavymachinegun_disrupt.lua b/gamedata/modularcomms/weapons/heavymachinegun_disrupt.lua index 8ed856ab54..6e95ba856d 100644 --- a/gamedata/modularcomms/weapons/heavymachinegun_disrupt.lua +++ b/gamedata/modularcomms/weapons/heavymachinegun_disrupt.lua @@ -1,4 +1,5 @@ local _, def = VFS.Include("gamedata/modularcomms/weapons/heavymachinegun.lua") +def.customParams.icon = [[unitpics/commweapon_slowbeam.png]] -- slow weapon def.name = "Disruptor " .. def.name def.customParams.timeslow_damagefactor = 2 diff --git a/gamedata/modularcomms/weapons/hparticlebeam.lua b/gamedata/modularcomms/weapons/hparticlebeam.lua index c0471b2ea4..e79a681a40 100644 --- a/gamedata/modularcomms/weapons/hparticlebeam.lua +++ b/gamedata/modularcomms/weapons/hparticlebeam.lua @@ -9,6 +9,8 @@ local weaponDef = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_hparticlebeam.png]], is_unit_weapon = 1, slot = [[5]], diff --git a/gamedata/modularcomms/weapons/hpartillery.lua b/gamedata/modularcomms/weapons/hpartillery.lua index 36b3765d95..156b053647 100644 --- a/gamedata/modularcomms/weapons/hpartillery.lua +++ b/gamedata/modularcomms/weapons/hpartillery.lua @@ -8,6 +8,8 @@ local weaponDef = { craterMult = 2, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_hpartillery.png]], is_unit_weapon = 1, muzzleEffectFire = [[custom:HEAVY_CANNON_MUZZLE]], miscEffectFire = [[custom:RIOT_SHELL_H]], diff --git a/gamedata/modularcomms/weapons/hpartillery_napalm.lua b/gamedata/modularcomms/weapons/hpartillery_napalm.lua index ce4ba924c1..4ea8689d5d 100644 --- a/gamedata/modularcomms/weapons/hpartillery_napalm.lua +++ b/gamedata/modularcomms/weapons/hpartillery_napalm.lua @@ -7,6 +7,8 @@ local weaponDef = { craterMult = 2, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_hpartillery.png]], is_unit_weapon = 1, muzzleEffectFire = [[custom:HEAVY_CANNON_MUZZLE]], burntime = [[60]], diff --git a/gamedata/modularcomms/weapons/lightninggun.lua b/gamedata/modularcomms/weapons/lightninggun.lua index 2f48e39311..9b05839248 100644 --- a/gamedata/modularcomms/weapons/lightninggun.lua +++ b/gamedata/modularcomms/weapons/lightninggun.lua @@ -7,6 +7,8 @@ local weaponDef = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_lightninggun.png]], is_unit_weapon = 1, extra_damage = 550, slot = [[5]], diff --git a/gamedata/modularcomms/weapons/lparticlebeam.lua b/gamedata/modularcomms/weapons/lparticlebeam.lua index 419902839f..00e4f69cff 100644 --- a/gamedata/modularcomms/weapons/lparticlebeam.lua +++ b/gamedata/modularcomms/weapons/lparticlebeam.lua @@ -9,6 +9,8 @@ local weaponDef = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_lparticlebeam.png]], is_unit_weapon = 1, slot = [[5]], diff --git a/gamedata/modularcomms/weapons/massdriver.lua b/gamedata/modularcomms/weapons/massdriver.lua index 825cc310b6..a13351191f 100644 --- a/gamedata/modularcomms/weapons/massdriver.lua +++ b/gamedata/modularcomms/weapons/massdriver.lua @@ -9,6 +9,8 @@ local weaponDef = { craterMult = 0, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_massdriver.png]], is_unit_weapon = 1, slot = [[5]], muzzleEffectFire = [[custom:RAIDMUZZLE]], diff --git a/gamedata/modularcomms/weapons/missilelauncher.lua b/gamedata/modularcomms/weapons/missilelauncher.lua index 24b28ca3e3..e4d80e719b 100644 --- a/gamedata/modularcomms/weapons/missilelauncher.lua +++ b/gamedata/modularcomms/weapons/missilelauncher.lua @@ -8,6 +8,8 @@ local weaponDef = { craterMult = 2, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], is_unit_weapon = 1, slot = [[5]], muzzleEffectFire = [[custom:SLASHMUZZLE]], diff --git a/gamedata/modularcomms/weapons/multistunner.lua b/gamedata/modularcomms/weapons/multistunner.lua index 15570b5092..54ca7bb27e 100644 --- a/gamedata/modularcomms/weapons/multistunner.lua +++ b/gamedata/modularcomms/weapons/multistunner.lua @@ -9,6 +9,8 @@ local weaponDef = { commandFire = true, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_multistunner.png]], is_unit_weapon = 1, muzzleEffectShot = [[custom:YELLOW_LIGHTNING_MUZZLE]], slot = [[3]], diff --git a/gamedata/modularcomms/weapons/napalmgrenade.lua b/gamedata/modularcomms/weapons/napalmgrenade.lua index 0bbf78aaaa..0fb3f2239b 100644 --- a/gamedata/modularcomms/weapons/napalmgrenade.lua +++ b/gamedata/modularcomms/weapons/napalmgrenade.lua @@ -9,6 +9,8 @@ local weaponDef = { craterMult = 0, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_napalmgrenade.png]], is_unit_weapon = 1, slot = [[3]], setunitsonfire = "1", diff --git a/gamedata/modularcomms/weapons/partillery.lua b/gamedata/modularcomms/weapons/partillery.lua index 49347704a7..27294c3fd1 100644 --- a/gamedata/modularcomms/weapons/partillery.lua +++ b/gamedata/modularcomms/weapons/partillery.lua @@ -5,6 +5,8 @@ local weaponDef = { areaOfEffect = 64, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_hpartillery.png]], is_unit_weapon = 1, muzzleEffectFire = [[custom:thud_fire_fx]], reaim_time = 1, diff --git a/gamedata/modularcomms/weapons/peashooter.lua b/gamedata/modularcomms/weapons/peashooter.lua index 6db6f18ba0..b06fc5a5a4 100644 --- a/gamedata/modularcomms/weapons/peashooter.lua +++ b/gamedata/modularcomms/weapons/peashooter.lua @@ -8,6 +8,8 @@ local weaponDef = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_heavymachinegun.png]], is_unit_weapon = 1, slot = [[5]], muzzleEffectShot = [[custom:BEAMWEAPON_MUZZLE_RED]], diff --git a/gamedata/modularcomms/weapons/riotcannon.lua b/gamedata/modularcomms/weapons/riotcannon.lua index d5cf4f00e8..689a5831af 100644 --- a/gamedata/modularcomms/weapons/riotcannon.lua +++ b/gamedata/modularcomms/weapons/riotcannon.lua @@ -10,6 +10,8 @@ local weaponDef = { craterMult = 2, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_riotcannon.png]], is_unit_weapon = 1, slot = [[5]], muzzleEffectFire = [[custom:HEAVY_CANNON_MUZZLE]], diff --git a/gamedata/modularcomms/weapons/rocketlauncher.lua b/gamedata/modularcomms/weapons/rocketlauncher.lua index c8cbc771a5..2fd059c76a 100644 --- a/gamedata/modularcomms/weapons/rocketlauncher.lua +++ b/gamedata/modularcomms/weapons/rocketlauncher.lua @@ -7,6 +7,8 @@ local weaponDef = { craterMult = 0, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_rocketlauncher.png]], is_unit_weapon = 1, slot = [[5]], muzzleEffectFire = [[custom:STORMMUZZLE]], diff --git a/gamedata/modularcomms/weapons/shockrifle.lua b/gamedata/modularcomms/weapons/shockrifle.lua index cf3c8f9768..5b8e95a16f 100644 --- a/gamedata/modularcomms/weapons/shockrifle.lua +++ b/gamedata/modularcomms/weapons/shockrifle.lua @@ -7,6 +7,8 @@ local weaponDef = { craterMult = 0, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_shockrifle.png]], is_unit_weapon = 1, slot = [[5]], light_radius = 0, diff --git a/gamedata/modularcomms/weapons/shotgun.lua b/gamedata/modularcomms/weapons/shotgun.lua index bdda4ef5da..582412970b 100644 --- a/gamedata/modularcomms/weapons/shotgun.lua +++ b/gamedata/modularcomms/weapons/shotgun.lua @@ -11,6 +11,8 @@ local weaponDef = { craterMult = 0, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], is_unit_weapon = 1, slot = [[5]], muzzleEffectFire = [[custom:HEAVY_CANNON_MUZZLE]], diff --git a/gamedata/modularcomms/weapons/shotgun_disrupt.lua b/gamedata/modularcomms/weapons/shotgun_disrupt.lua index af88559407..b2bedb5088 100644 --- a/gamedata/modularcomms/weapons/shotgun_disrupt.lua +++ b/gamedata/modularcomms/weapons/shotgun_disrupt.lua @@ -1,4 +1,5 @@ local _, def = VFS.Include("gamedata/modularcomms/weapons/shotgun.lua") +def.customParams.icon = [[unitpics/commweapon_slowbeam.png]] -- slow weapon def.name = "Disruptor " .. def.name def.customParams.timeslow_damagefactor = 2 diff --git a/gamedata/modularcomms/weapons/shotlaser.lua b/gamedata/modularcomms/weapons/shotlaser.lua index 071935be42..23d55fd8c6 100644 --- a/gamedata/modularcomms/weapons/shotlaser.lua +++ b/gamedata/modularcomms/weapons/shotlaser.lua @@ -9,6 +9,8 @@ local weaponDef = { craterMult = 0, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_beamlaser.png]], is_unit_weapon = 1, slot = [[5]], muzzleEffectFire = [[custom:HEAVY_CANNON_MUZZLE]], diff --git a/gamedata/modularcomms/weapons/shotlaser_disrupt.lua b/gamedata/modularcomms/weapons/shotlaser_disrupt.lua index 783214067d..a6e88c1046 100644 --- a/gamedata/modularcomms/weapons/shotlaser_disrupt.lua +++ b/gamedata/modularcomms/weapons/shotlaser_disrupt.lua @@ -1,4 +1,5 @@ local _, def = VFS.Include("gamedata/modularcomms/weapons/shotlaser.lua") +def.customParams.icon = [[unitpics/commweapon_slowbeam.png]] -- slow weapon def.name = "Disruptor " .. def.name def.customParams.timeslow_damagefactor = 2 diff --git a/gamedata/modularcomms/weapons/slamrocket.lua b/gamedata/modularcomms/weapons/slamrocket.lua index a8b6675d2a..67ef856bac 100644 --- a/gamedata/modularcomms/weapons/slamrocket.lua +++ b/gamedata/modularcomms/weapons/slamrocket.lua @@ -15,6 +15,8 @@ local weaponDef = { craterMult = 1.0, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_slamrocket.png]], is_unit_weapon = 1, slot = [[3]], muzzleEffectFire = [[custom:SLAM_MUZZLE]], diff --git a/gamedata/modularcomms/weapons/slowbeam.lua b/gamedata/modularcomms/weapons/slowbeam.lua index b83519d3e2..d1fddc0679 100644 --- a/gamedata/modularcomms/weapons/slowbeam.lua +++ b/gamedata/modularcomms/weapons/slowbeam.lua @@ -10,6 +10,8 @@ local weaponDef = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_slowbeam.png]], is_unit_weapon = 1, slot = [[5]], timeslow_onlyslow = [[1]], diff --git a/gamedata/modularcomms/weapons/sonicgun.lua b/gamedata/modularcomms/weapons/sonicgun.lua index 4294419271..db73db259a 100644 --- a/gamedata/modularcomms/weapons/sonicgun.lua +++ b/gamedata/modularcomms/weapons/sonicgun.lua @@ -9,6 +9,8 @@ local weaponDef = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_sonicgun.png]], is_unit_weapon = 1, slot = [[5]], lups_explodelife = 100, diff --git a/gamedata/modularcomms/weapons/sunburst.lua b/gamedata/modularcomms/weapons/sunburst.lua index f4e7bd993c..83a7e2c7c7 100644 --- a/gamedata/modularcomms/weapons/sunburst.lua +++ b/gamedata/modularcomms/weapons/sunburst.lua @@ -8,6 +8,8 @@ local weaponDef = { craterMult = 6, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_sunburst.png]], is_unit_weapon = 1, slot = [[3]], muzzleEffectFire = [[custom:staticheavyarty_FLARE]], diff --git a/gamedata/modularcomms/weapons/torpedo.lua b/gamedata/modularcomms/weapons/torpedo.lua index a93f2f90e4..a55f800bf0 100644 --- a/gamedata/modularcomms/weapons/torpedo.lua +++ b/gamedata/modularcomms/weapons/torpedo.lua @@ -11,6 +11,8 @@ local weaponDef = { craterMult = 0, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_torpedo.png]], is_unit_weapon = 1, badTargetCategory = [[FIXEDWING]], onlyTargetCategory = [[SWIM FIXEDWING LAND SUB SINK TURRET FLOAT SHIP GUNSHIP HOVER]], diff --git a/units/amphaa.lua b/units/amphaa.lua index 80423730b2..a70376dcc6 100644 --- a/units/amphaa.lua +++ b/units/amphaa.lua @@ -85,6 +85,8 @@ return { amphaa = { cylinderTargeting = 1, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], burst = Shared.BURST_RELIABLE, isaa = [[1]], @@ -135,6 +137,8 @@ return { amphaa = { cylinderTargeting = 1, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], burst = Shared.BURST_RELIABLE, isaa = [[1]], diff --git a/units/amphassault.lua b/units/amphassault.lua index 8e71f8783d..e08449c273 100644 --- a/units/amphassault.lua +++ b/units/amphassault.lua @@ -92,6 +92,8 @@ return { amphassault = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], burst = Shared.BURST_UNRELIABLE, light_color = [[0.5 0.5 1.5]], @@ -146,6 +148,8 @@ return { amphassault = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], bogus = 1, }, diff --git a/units/amphfloater.lua b/units/amphfloater.lua index cd3d3ce46c..ee80327b6a 100644 --- a/units/amphfloater.lua +++ b/units/amphfloater.lua @@ -87,6 +87,8 @@ return { amphfloater = { craterMult = 2, customparams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_slowbeam.png]], burst = Shared.BURST_RELIABLE, timeslow_damagefactor = 1.667, @@ -130,6 +132,8 @@ return { amphfloater = { craterMult = 2, customparams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_slowbeam.png]], timeslow_damagefactor = 1.7, bogus = 1, }, diff --git a/units/amphimpulse.lua b/units/amphimpulse.lua index a4190fba7b..934cf62980 100644 --- a/units/amphimpulse.lua +++ b/units/amphimpulse.lua @@ -81,6 +81,8 @@ return { amphimpulse = { craterMult = 0, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], force_ignore_ground = [[1]], lups_explodelife = 1.0, lups_explodespeed = 0.4, diff --git a/units/amphlaunch.lua b/units/amphlaunch.lua index 8bf5ad8131..696f371204 100644 --- a/units/amphlaunch.lua +++ b/units/amphlaunch.lua @@ -85,6 +85,8 @@ return { amphlaunch = { craterMult = 0, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], ui_manual_fire = 1, lups_noshockwave = [[1]], thower_weapon = 1, @@ -125,6 +127,8 @@ return { amphlaunch = { craterMult = 0, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], lups_noshockwave = [[1]], thower_weapon = 1, bogus = 1, diff --git a/units/amphraid.lua b/units/amphraid.lua index 9364d6f4e3..3346075aad 100644 --- a/units/amphraid.lua +++ b/units/amphraid.lua @@ -75,6 +75,8 @@ return { amphraid = { craterMult = 2, customparams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], burst = Shared.BURST_RELIABLE, light_color = [[1 0.6 0.2]], @@ -124,6 +126,8 @@ return { amphraid = { cegTag = [[torpedo_trail]], customparams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], radar_homing_distance = 200, stays_underwater = 1, }, diff --git a/units/amphriot.lua b/units/amphriot.lua index a60a05c24e..150acf154c 100644 --- a/units/amphriot.lua +++ b/units/amphriot.lua @@ -92,6 +92,8 @@ return { amphriot = { cegTag = [[torpedo_trail]], customparams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], stays_underwater = 1, }, @@ -138,6 +140,8 @@ return { amphriot = { craterMult = 0, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], light_camera_height = 2000, light_color = [[0.3 0.3 0.05]], light_radius = 50, diff --git a/units/amphsupport.lua b/units/amphsupport.lua index 81900f4d04..d70272017a 100644 --- a/units/amphsupport.lua +++ b/units/amphsupport.lua @@ -93,6 +93,8 @@ return { amphsupport = { craterMult = 0, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], light_camera_height = 1400, light_color = [[0.80 0.54 0.23]], light_radius = 230, @@ -129,6 +131,8 @@ return { amphsupport = { craterMult = 0, customparams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], bogus = 1, }, diff --git a/units/armcom1.lua b/units/armcom1.lua index fb636b90f7..61455bc4b2 100644 --- a/units/armcom1.lua +++ b/units/armcom1.lua @@ -106,6 +106,8 @@ return { armcom1 = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], bogus = 1, }, @@ -177,6 +179,10 @@ return { armcom1 = { thickness = 3, tolerance = 10000, turret = true, + customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], + }, weaponType = [[BeamLaser]], weaponVelocity = 900, }, diff --git a/units/assaultcruiser.lua b/units/assaultcruiser.lua index 25a61a6c8f..6378a23d1d 100644 --- a/units/assaultcruiser.lua +++ b/units/assaultcruiser.lua @@ -137,6 +137,10 @@ return { assaultcruiser = { texture4 = [[smallflare]], thickness = 5, turret = true, + customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], + }, weaponType = [[BeamLaser]], }, @@ -177,6 +181,10 @@ return { assaultcruiser = { stages = 32, turret = true, waterbounce = 1, + customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], + }, weaponType = [[Cannon]], weaponVelocity = 2400, }, @@ -217,6 +225,10 @@ return { assaultcruiser = { turret = true, waterWeapon = true, weaponAcceleration = 300, + customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], + }, weaponType = [[MissileLauncher]], weaponVelocity = 600, }, diff --git a/units/benzcom1.lua b/units/benzcom1.lua index c0ae7c0ea3..b8b03a157f 100644 --- a/units/benzcom1.lua +++ b/units/benzcom1.lua @@ -102,6 +102,8 @@ return { benzcom1 = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], bogus = 1, }, @@ -156,6 +158,10 @@ return { benzcom1 = { soundHit = [[weapon/cannon/cannon_hit2]], soundStart = [[weapon/cannon/medplasma_fire]], turret = true, + customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], + }, weaponType = [[Cannon]], weaponVelocity = 300, }, diff --git a/units/bomberassault.lua b/units/bomberassault.lua index 4ae77ea012..4a430ab3a2 100644 --- a/units/bomberassault.lua +++ b/units/bomberassault.lua @@ -103,6 +103,10 @@ return { bomberassault = { reloadtime = 1, soundHit = [[weapon/missile/liche_hit]], soundStart = [[weapon/missile/liche_fire]], + customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], + }, weaponType = [[AircraftBomb]], }, @@ -117,6 +121,8 @@ return { bomberassault = { craterMult = 0, customparams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], reammoseconds = "autogenerated in posts", burst = Shared.BURST_UNRELIABLE, stats_burst_damage = 14000, @@ -180,6 +186,8 @@ return { bomberassault = { craterMult = 0, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], reammoseconds = "autogenerated in posts", damage_vs_shield = [[3400]], -- Same damage as shield charge? Reversal of polarity? diff --git a/units/bomberdisarm.lua b/units/bomberdisarm.lua index ab7420a283..9016ec79c5 100644 --- a/units/bomberdisarm.lua +++ b/units/bomberdisarm.lua @@ -76,6 +76,8 @@ return { bomberdisarm = { craterMult = 0, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], reammoseconds = "autogenerated in posts", reaim_time = 15, -- Fast update not required (maybe dangerous) disarmDamageMult = 1, @@ -126,6 +128,8 @@ return { bomberdisarm = { collideFriendly = false, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], bogus = 1, }, diff --git a/units/bomberheavy.lua b/units/bomberheavy.lua index 28747347ba..4635c901ea 100644 --- a/units/bomberheavy.lua +++ b/units/bomberheavy.lua @@ -80,6 +80,8 @@ return { bomberheavy = { craterMult = 2, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], burst = Shared.BURST_UNRELIABLE, reammoseconds = "autogenerated in posts", diff --git a/units/bomberheavyold.lua b/units/bomberheavyold.lua index edac68fd05..a57edf6c59 100644 --- a/units/bomberheavyold.lua +++ b/units/bomberheavyold.lua @@ -78,6 +78,8 @@ return { bomberheavyold = { craterMult = 2, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], burst = Shared.BURST_UNRELIABLE, reammoseconds = "autogenerated in posts", diff --git a/units/bomberprec.lua b/units/bomberprec.lua index 61aaf68aab..ff03fd0885 100644 --- a/units/bomberprec.lua +++ b/units/bomberprec.lua @@ -93,6 +93,8 @@ return { bomberprec = { craterMult = 0, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], bogus = 1, }, @@ -131,6 +133,8 @@ return { bomberprec = { }, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], reammoseconds = "autogenerated in posts", reaim_time = 15, -- Fast update not required (maybe dangerous) light_color = [[1.1 0.9 0.45]], @@ -179,6 +183,8 @@ return { bomberprec = { craterMult = 0, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], reaim_time = 15, -- Fast update not required (maybe dangerous) bogus = 1, }, @@ -216,6 +222,8 @@ return { bomberprec = { craterMult = 2, customparams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], stays_underwater = 1, }, diff --git a/units/bomberriot.lua b/units/bomberriot.lua index 09cf943109..6cd08fa56f 100644 --- a/units/bomberriot.lua +++ b/units/bomberriot.lua @@ -85,6 +85,8 @@ return { bomberriot = { craterMult = 0, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], reammoseconds = "autogenerated in posts", reaim_time = 15, -- Fast update not required (maybe dangerous) setunitsonfire = "1", diff --git a/units/bomberstrike.lua b/units/bomberstrike.lua index 4ea96e3b52..9859ed25f3 100644 --- a/units/bomberstrike.lua +++ b/units/bomberstrike.lua @@ -73,6 +73,8 @@ return { bomberstrike = { craterMult = 2, customparams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], burst = Shared.BURST_RELIABLE, reammoseconds = "autogenerated in posts", }, diff --git a/units/chicken.lua b/units/chicken.lua index c266520c9d..e2fad3520e 100644 --- a/units/chicken.lua +++ b/units/chicken.lua @@ -101,6 +101,10 @@ return { chicken = { tolerance = 5000, turret = true, waterWeapon = true, + customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], + }, weaponType = [[Cannon]], weaponVelocity = 500, }, diff --git a/units/chicken_blimpy.lua b/units/chicken_blimpy.lua index 570474f886..86004ab7dc 100644 --- a/units/chicken_blimpy.lua +++ b/units/chicken_blimpy.lua @@ -93,6 +93,8 @@ return { chicken_blimpy = { craterMult = 0, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], bogus = 1, }, @@ -143,6 +145,10 @@ return { chicken_blimpy = { thickness = 0, tolerance = 10000, turret = true, + customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], + }, weaponType = [[BeamLaser]], weaponVelocity = 100, }, @@ -162,6 +168,8 @@ return { chicken_blimpy = { craterMult = 0, customparams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], spawns_name = "chicken_dodo", spawns_expire = 30, }, diff --git a/units/chicken_digger.lua b/units/chicken_digger.lua index 5a519a3d07..9f61af8f54 100644 --- a/units/chicken_digger.lua +++ b/units/chicken_digger.lua @@ -98,6 +98,10 @@ return { chicken_digger = { tolerance = 5000, turret = true, waterWeapon = true, + customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], + }, weaponType = [[Cannon]], weaponVelocity = 500, }, diff --git a/units/chicken_dragon.lua b/units/chicken_dragon.lua index 4fe2cc61d9..8abba06301 100644 --- a/units/chicken_dragon.lua +++ b/units/chicken_dragon.lua @@ -125,6 +125,8 @@ return { chicken_dragon = { craterMult = 0, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], light_radius = 0, }, @@ -176,6 +178,10 @@ return { chicken_dragon = { tolerance = 5000, turret = true, waterWeapon = true, + customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], + }, weaponType = [[Cannon]], weaponVelocity = 600, }, @@ -207,6 +213,10 @@ return { chicken_dragon = { thickness = 1, tolerance = 100, turret = true, + customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], + }, weaponType = [[Cannon]], weaponVelocity = 0.8, }, @@ -223,6 +233,8 @@ return { chicken_dragon = { craterMult = 0, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], light_radius = 0, }, diff --git a/units/chicken_leaper.lua b/units/chicken_leaper.lua index aae26e701a..4179f2d0bc 100644 --- a/units/chicken_leaper.lua +++ b/units/chicken_leaper.lua @@ -100,6 +100,10 @@ return { chicken_leaper = { tolerance = 5000, turret = true, waterWeapon = true, + customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], + }, weaponType = [[LaserCannon]], weaponVelocity = 3000, }, diff --git a/units/chicken_pigeon.lua b/units/chicken_pigeon.lua index be9534bacc..e6309b72f2 100644 --- a/units/chicken_pigeon.lua +++ b/units/chicken_pigeon.lua @@ -85,6 +85,8 @@ return { chicken_pigeon = { craterMult = 0, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], bogus = 1, }, @@ -115,6 +117,8 @@ return { chicken_pigeon = { craterMult = 0, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], light_radius = 0, }, diff --git a/units/chicken_roc.lua b/units/chicken_roc.lua index 64c598825e..bd244cd686 100644 --- a/units/chicken_roc.lua +++ b/units/chicken_roc.lua @@ -98,6 +98,8 @@ return { chicken_roc = { craterMult = 0, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], light_radius = 0, }, @@ -138,6 +140,8 @@ return { chicken_roc = { craterMult = 0, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], light_radius = 0, }, diff --git a/units/chicken_shield.lua b/units/chicken_shield.lua index 1efc865269..ecc127b678 100644 --- a/units/chicken_shield.lua +++ b/units/chicken_shield.lua @@ -98,6 +98,8 @@ return { chicken_shield = { craterMult = 0, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], light_radius = 0, }, @@ -165,6 +167,10 @@ return { chicken_shield = { tolerance = 5000, turret = true, waterWeapon = false, + customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], + }, weaponType = [[Cannon]], weaponVelocity = 500, }, diff --git a/units/chicken_spidermonkey.lua b/units/chicken_spidermonkey.lua index c253b6712d..f9f5504e44 100644 --- a/units/chicken_spidermonkey.lua +++ b/units/chicken_spidermonkey.lua @@ -77,6 +77,8 @@ return { chicken_spidermonkey = { accuracy = 800, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_slowbeam.png]], impulse = [[-100]], timeslow_onlyslow = 1, timeslow_smartretarget = 0.33, diff --git a/units/chicken_sporeshooter.lua b/units/chicken_sporeshooter.lua index 82ea2450a7..bc112c50ce 100644 --- a/units/chicken_sporeshooter.lua +++ b/units/chicken_sporeshooter.lua @@ -83,6 +83,8 @@ return { chicken_sporeshooter = { craterMult = 0, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], light_radius = 0, }, diff --git a/units/chicken_tiamat.lua b/units/chicken_tiamat.lua index 3d602720f0..4558997114 100644 --- a/units/chicken_tiamat.lua +++ b/units/chicken_tiamat.lua @@ -115,6 +115,8 @@ return { chicken_tiamat = { cegTag = [[flamer_240_range]], customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], flamethrower = [[1]], setunitsonfire = "1", burntime = [[450]], @@ -181,6 +183,10 @@ return { chicken_tiamat = { tolerance = 5000, turret = true, waterWeapon = true, + customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], + }, weaponType = [[Cannon]], weaponVelocity = 500, }, @@ -228,6 +234,8 @@ return { chicken_tiamat = { craterMult = 0, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], light_radius = 0, }, diff --git a/units/chickena.lua b/units/chickena.lua index 0d3e7d783b..98ed56d42e 100644 --- a/units/chickena.lua +++ b/units/chickena.lua @@ -97,6 +97,10 @@ return { chickena = { tolerance = 5000, turret = true, waterWeapon = true, + customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], + }, weaponType = [[Cannon]], weaponVelocity = 1000, }, diff --git a/units/chickenblobber.lua b/units/chickenblobber.lua index 0c83cb029d..836e6f1287 100644 --- a/units/chickenblobber.lua +++ b/units/chickenblobber.lua @@ -83,6 +83,8 @@ return { chickenblobber = { craterMult = 0, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], light_radius = 0, }, diff --git a/units/chickenbroodqueen.lua b/units/chickenbroodqueen.lua index dfac437ca4..bedf321464 100644 --- a/units/chickenbroodqueen.lua +++ b/units/chickenbroodqueen.lua @@ -146,6 +146,10 @@ return { chickenbroodqueen = { tolerance = 5000, turret = true, waterWeapon = true, + customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], + }, weaponType = [[Cannon]], weaponVelocity = 500, }, @@ -162,6 +166,8 @@ return { chickenbroodqueen = { craterMult = 0, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], light_radius = 0, }, diff --git a/units/chickenc.lua b/units/chickenc.lua index 9aab890537..9f3eb5b986 100644 --- a/units/chickenc.lua +++ b/units/chickenc.lua @@ -102,6 +102,10 @@ return { chickenc = { sprayAngle = 1024, tolerance = 5000, turret = true, + customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], + }, weaponType = [[Cannon]], waterWeapon = true, weaponVelocity = 400, diff --git a/units/chickend.lua b/units/chickend.lua index 214a9cbbcd..060f5e35bf 100644 --- a/units/chickend.lua +++ b/units/chickend.lua @@ -76,6 +76,8 @@ return { chickend = { craterMult = 0, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], light_radius = 0, }, diff --git a/units/chickenf.lua b/units/chickenf.lua index efb50dd277..464ebc3af4 100644 --- a/units/chickenf.lua +++ b/units/chickenf.lua @@ -84,6 +84,8 @@ return { chickenf = { craterMult = 0, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], light_radius = 0, }, diff --git a/units/chickenflyerqueen.lua b/units/chickenflyerqueen.lua index 900498f262..93da87cb5d 100644 --- a/units/chickenflyerqueen.lua +++ b/units/chickenflyerqueen.lua @@ -128,6 +128,8 @@ return { chickenflyerqueen = { craterMult = 2, customparams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], spawns_name = "chickenc", spawns_expire = 0, }, @@ -170,6 +172,8 @@ return { chickenflyerqueen = { craterMult = 0, customparams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], spawns_name = "chicken_dodo", spawns_expire = 30, }, @@ -211,6 +215,8 @@ return { chickenflyerqueen = { craterMult = 0, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], light_radius = 0, }, @@ -252,6 +258,8 @@ return { chickenflyerqueen = { craterMult = 0, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], light_radius = 0, }, @@ -305,6 +313,8 @@ return { chickenflyerqueen = { craterMult = 2, customparams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], spawns_name = "chicken_tiamat", spawns_expire = 0, }, diff --git a/units/chickenlandqueen.lua b/units/chickenlandqueen.lua index d7bd1c4806..18affcdc15 100644 --- a/units/chickenlandqueen.lua +++ b/units/chickenlandqueen.lua @@ -143,6 +143,8 @@ return { chickenlandqueen = { craterMult = 2, customparams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], spawns_name = "chickenc", spawns_expire = 0, }, @@ -185,6 +187,8 @@ return { chickenlandqueen = { craterMult = 0, customparams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], spawns_name = "chicken_dodo", spawns_expire = 30, }, @@ -227,6 +231,8 @@ return { chickenlandqueen = { craterMult = 2, customparams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], spawns_name = "chicken_tiamat", spawns_expire = 0, }, @@ -265,6 +271,8 @@ return { chickenlandqueen = { cegTag = [[queen_trail_fire]], customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], light_radius = 500, }, @@ -323,6 +331,10 @@ return { chickenlandqueen = { tolerance = 5000, turret = true, waterWeapon = true, + customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], + }, weaponType = [[Cannon]], weaponVelocity = 600, }, @@ -336,6 +348,8 @@ return { chickenlandqueen = { craterMult = 0.002, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], lups_noshockwave = "1", }, @@ -375,6 +389,8 @@ return { chickenlandqueen = { craterMult = 0, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], light_radius = 0, }, diff --git a/units/chickenr.lua b/units/chickenr.lua index 4aa06929a8..625cb28d65 100644 --- a/units/chickenr.lua +++ b/units/chickenr.lua @@ -83,6 +83,8 @@ return { chickenr = { craterMult = 0, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], light_radius = 0, }, diff --git a/units/chickens.lua b/units/chickens.lua index 9f6ad1f7aa..c545d75704 100644 --- a/units/chickens.lua +++ b/units/chickens.lua @@ -88,6 +88,8 @@ return { chickens = { craterMult = 0, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], light_radius = 0, }, diff --git a/units/chickenspire.lua b/units/chickenspire.lua index 5d0e6f67b7..2d5d2fd092 100644 --- a/units/chickenspire.lua +++ b/units/chickenspire.lua @@ -80,6 +80,8 @@ return { chickenspire = { craterMult = 2, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], light_radius = 0, }, diff --git a/units/chickenwurm.lua b/units/chickenwurm.lua index 4de579a22b..82e03f8b4f 100644 --- a/units/chickenwurm.lua +++ b/units/chickenwurm.lua @@ -94,6 +94,8 @@ return { chickenwurm = { craterMult = 0, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], setunitsonfire = "1", burntime = 180, @@ -135,6 +137,8 @@ return { chickenwurm = { craterMult = 0, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], }, damage = { diff --git a/units/cloakaa.lua b/units/cloakaa.lua index 3b756ce959..c86440773c 100644 --- a/units/cloakaa.lua +++ b/units/cloakaa.lua @@ -86,6 +86,8 @@ return { cloakaa = { cylinderTargeting = 1, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], isaa = [[1]], light_radius = 80, light_alpha = 0.08, diff --git a/units/cloakarty.lua b/units/cloakarty.lua index 93ac49d638..7759efc0c5 100644 --- a/units/cloakarty.lua +++ b/units/cloakarty.lua @@ -81,6 +81,8 @@ return { cloakarty = { craterMult = 0, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], burst = Shared.BURST_RELIABLE, light_camera_height = 1400, diff --git a/units/cloakassault.lua b/units/cloakassault.lua index cb4a6264d9..c5602fd8f9 100644 --- a/units/cloakassault.lua +++ b/units/cloakassault.lua @@ -77,6 +77,8 @@ return { cloakassault = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], extra_damage = 720, light_camera_height = 1600, diff --git a/units/cloakheavyraid.lua b/units/cloakheavyraid.lua index 6b31d40265..ab594e1542 100644 --- a/units/cloakheavyraid.lua +++ b/units/cloakheavyraid.lua @@ -84,6 +84,8 @@ return { cloakheavyraid = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], light_camera_height = 500, light_color = [[1 1 0.7]], light_radius = 120, diff --git a/units/cloakraid.lua b/units/cloakraid.lua index 2856aa6239..713ad0eda9 100644 --- a/units/cloakraid.lua +++ b/units/cloakraid.lua @@ -82,6 +82,8 @@ return { cloakraid = { craterMult = 0, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], light_camera_height = 1200, light_color = [[0.8 0.76 0.38]], light_radius = 120, diff --git a/units/cloakriot.lua b/units/cloakriot.lua index c318806484..c06783c49c 100644 --- a/units/cloakriot.lua +++ b/units/cloakriot.lua @@ -91,6 +91,8 @@ return { cloakriot = { craterMult = 0.3, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], reaim_time = 1, -- noticeable twitching otherwise due to huge turnrates light_camera_height = 1600, light_color = [[0.8 0.76 0.38]], diff --git a/units/cloakskirm.lua b/units/cloakskirm.lua index 9a5ca1885a..7e0b00e645 100644 --- a/units/cloakskirm.lua +++ b/units/cloakskirm.lua @@ -78,6 +78,8 @@ return { cloakskirm = { craterMult = 0, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], reaim_time = 1, -- Keep aiming at target to prevent sideways gun, which can lead to teamkill. burst = Shared.BURST_RELIABLE, diff --git a/units/cloaksnipe.lua b/units/cloaksnipe.lua index c3bd368941..76a0c17757 100644 --- a/units/cloaksnipe.lua +++ b/units/cloaksnipe.lua @@ -90,6 +90,8 @@ return { cloaksnipe = { craterMult = 0, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], reaim_time = 1, -- Keep aiming at target to prevent sideways gun, which can lead to teamkill. burst = Shared.BURST_RELIABLE, light_radius = 0, diff --git a/units/commrecon1.lua b/units/commrecon1.lua index 4e6e4f549b..9ec98a81a0 100644 --- a/units/commrecon1.lua +++ b/units/commrecon1.lua @@ -113,6 +113,8 @@ return { commrecon1 = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], bogus = 1, }, @@ -154,6 +156,8 @@ return { commrecon1 = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_slowbeam.png]], timeslow_onlyslow = 1, timeslow_smartretarget = 0.33, }, diff --git a/units/commstrike1.lua b/units/commstrike1.lua index 00451f6cf6..0e60080e2e 100644 --- a/units/commstrike1.lua +++ b/units/commstrike1.lua @@ -127,6 +127,10 @@ return { commstrike1 = { thickness = 5.53, tolerance = 10000, turret = true, + customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], + }, weaponType = [[BeamLaser]], weaponVelocity = 900, }, @@ -167,6 +171,10 @@ return { commstrike1 = { thickness = 3, tolerance = 10000, turret = true, + customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], + }, weaponType = [[BeamLaser]], weaponVelocity = 900, }, diff --git a/units/commsupport1.lua b/units/commsupport1.lua index 77551ece3e..d65f6b9645 100644 --- a/units/commsupport1.lua +++ b/units/commsupport1.lua @@ -104,6 +104,8 @@ return { commsupport1 = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], bogus = 1, }, @@ -151,6 +153,8 @@ return { commsupport1 = { }, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], single_hit = true, }, diff --git a/units/corcom1.lua b/units/corcom1.lua index 9543e06712..a9498c3397 100644 --- a/units/corcom1.lua +++ b/units/corcom1.lua @@ -106,6 +106,8 @@ return { corcom1 = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], bogus = 1, }, @@ -169,6 +171,10 @@ return { corcom1 = { soundStart = [[weapon/cannon/outlaw_gun]], soundStartVolume = 3, turret = true, + customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], + }, weaponType = [[Cannon]], weaponVelocity = 750, }, diff --git a/units/cremcom1.lua b/units/cremcom1.lua index 51c93d9497..c15f1e97a7 100644 --- a/units/cremcom1.lua +++ b/units/cremcom1.lua @@ -106,6 +106,8 @@ return { cremcom1 = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], bogus = 1, }, @@ -178,6 +180,10 @@ return { cremcom1 = { thickness = 3, tolerance = 10000, turret = true, + customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], + }, weaponType = [[BeamLaser]], weaponVelocity = 900, }, diff --git a/units/dronecarry.lua b/units/dronecarry.lua index 59456dc3f9..79df3b4d01 100644 --- a/units/dronecarry.lua +++ b/units/dronecarry.lua @@ -83,6 +83,8 @@ return { dronecarry = { craterMult = 0, customparams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], capture_scaling = 1, is_capture = 1, capture_to_drone_controller = 1, diff --git a/units/dronefighter.lua b/units/dronefighter.lua index e47ccab9cd..68832c28b9 100644 --- a/units/dronefighter.lua +++ b/units/dronefighter.lua @@ -108,6 +108,10 @@ return { dronefighter = { thickness = 2, tolerance = 8192, turret = true, + customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], + }, weaponType = [[BeamLaser]], }, }, diff --git a/units/droneheavyslow.lua b/units/droneheavyslow.lua index 069775ac92..112ee09085 100644 --- a/units/droneheavyslow.lua +++ b/units/droneheavyslow.lua @@ -77,6 +77,8 @@ return { droneheavyslow = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_slowbeam.png]], timeslow_damagefactor = [[2]], light_camera_height = 2000, diff --git a/units/dronelight.lua b/units/dronelight.lua index d367e4782d..7e584619a7 100644 --- a/units/dronelight.lua +++ b/units/dronelight.lua @@ -78,6 +78,8 @@ return { dronelight = { cylinderTargeting = 1, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], light_camera_height = 1800, light_color = [[0.25 1 0.25]], light_radius = 130, diff --git a/units/empmissile.lua b/units/empmissile.lua index b83a5c077c..cb5f9f7bd9 100644 --- a/units/empmissile.lua +++ b/units/empmissile.lua @@ -62,6 +62,8 @@ return { empmissile = { craterMult = 0, customparams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], burst = Shared.BURST_RELIABLE, stats_hide_dps = 1, -- one use diff --git a/units/grebe.lua b/units/grebe.lua index 2b72f1bd9d..e6eb75d146 100644 --- a/units/grebe.lua +++ b/units/grebe.lua @@ -84,6 +84,10 @@ return { grebe = { soundTrigger = true, sprayangle = 512, turret = true, + customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], + }, weaponType = [[Cannon]], weaponVelocity = 400, }, diff --git a/units/gunshipaa.lua b/units/gunshipaa.lua index b94ad0c4c6..386c458b64 100644 --- a/units/gunshipaa.lua +++ b/units/gunshipaa.lua @@ -78,6 +78,8 @@ return { gunshipaa = { cylinderTargeting = 1, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], burst = Shared.BURST_RELIABLE, isaa = [[1]], diff --git a/units/gunshipassault.lua b/units/gunshipassault.lua index fc79e82734..1d9aac8bdb 100644 --- a/units/gunshipassault.lua +++ b/units/gunshipassault.lua @@ -70,6 +70,8 @@ return { gunshipassault = { craterMult = 0.246, customparams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], burst = Shared.BURST_UNRELIABLE, light_camera_height = 2500, diff --git a/units/gunshipbomb.lua b/units/gunshipbomb.lua index 37b3a26763..3f830aed91 100644 --- a/units/gunshipbomb.lua +++ b/units/gunshipbomb.lua @@ -96,6 +96,8 @@ return { gunshipbomb = { craterMult = 3.5, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], setunitsonfire = "1", burnchance = "1", burntime = 720, @@ -136,6 +138,8 @@ return { gunshipbomb = { craterMult = 3.5, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], setunitsonfire = "1", burnchance = "1", burntime = 360, diff --git a/units/gunshipemp.lua b/units/gunshipemp.lua index b017c7ff83..4f50ba5fdd 100644 --- a/units/gunshipemp.lua +++ b/units/gunshipemp.lua @@ -73,6 +73,8 @@ return { gunshipemp = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], combatrange = 70, light_camera_height = 1000, light_color = [[1 1 0.4]], diff --git a/units/gunshipheavyskirm.lua b/units/gunshipheavyskirm.lua index 06f0111a05..cb1a473f75 100644 --- a/units/gunshipheavyskirm.lua +++ b/units/gunshipheavyskirm.lua @@ -85,6 +85,8 @@ return { gunshipheavyskirm = { craterMult = 0.3, customparams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], combatrange = 630, light_camera_height = 2000, light_color = [[0.9 0.84 0.45]], diff --git a/units/gunshipheavytrans.lua b/units/gunshipheavytrans.lua index ad1a7e5c6e..045d6ba1e8 100644 --- a/units/gunshipheavytrans.lua +++ b/units/gunshipheavytrans.lua @@ -113,6 +113,8 @@ return { gunshipheavytrans = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], combatrange = 60, light_camera_height = 1200, light_radius = 160, @@ -157,6 +159,8 @@ return { gunshipheavytrans = { cylinderTargeting = 1, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], combatrange = 100, light_radius = 80, light_alpha = 0.08, diff --git a/units/gunshipkrow.lua b/units/gunshipkrow.lua index c7c5b00939..9454a40143 100644 --- a/units/gunshipkrow.lua +++ b/units/gunshipkrow.lua @@ -101,6 +101,8 @@ return { gunshipkrow = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], burst = Shared.BURST_UNRELIABLE, light_color = [[0.4 0.85 1]], light_radius = 110, @@ -172,6 +174,10 @@ return { gunshipkrow = { soundStartVolume = 2, sprayangle = 13500, turret = true, + customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], + }, weaponType = [[Cannon]], weaponVelocity = 400, }, diff --git a/units/gunshipraid.lua b/units/gunshipraid.lua index 865c3bccb7..9350d22993 100644 --- a/units/gunshipraid.lua +++ b/units/gunshipraid.lua @@ -77,6 +77,8 @@ return { gunshipraid = { --cylinderTargeting = 1, customparams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], stats_hide_damage = 1, -- continuous laser stats_hide_reload = 1, diff --git a/units/gunshipskirm.lua b/units/gunshipskirm.lua index 779cbd35e7..3db3f796fa 100644 --- a/units/gunshipskirm.lua +++ b/units/gunshipskirm.lua @@ -69,6 +69,8 @@ return { gunshipskirm = { craterMult = 0, customparams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_slowbeam.png]], burst = Shared.BURST_RELIABLE, timeslow_damagefactor = 3, diff --git a/units/hoveraa.lua b/units/hoveraa.lua index c3613de744..5b77ff88db 100644 --- a/units/hoveraa.lua +++ b/units/hoveraa.lua @@ -78,6 +78,8 @@ return { hoveraa = { cylinderTargeting = 1, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], burst = Shared.BURST_RELIABLE, isaa = [[1]], diff --git a/units/hoverarty.lua b/units/hoverarty.lua index c912b3d5cd..a7d5a28679 100644 --- a/units/hoverarty.lua +++ b/units/hoverarty.lua @@ -79,6 +79,8 @@ return { hoverarty = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], burst = Shared.BURST_RELIABLE, light_color = [[1.25 0.8 1.75]], diff --git a/units/hoverassault.lua b/units/hoverassault.lua index 6ac48a57c5..18cfe6ac8d 100644 --- a/units/hoverassault.lua +++ b/units/hoverassault.lua @@ -76,6 +76,8 @@ return { hoverassault = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], light_camera_height = 1600, light_color = [[0.7 0.7 2.3]], light_radius = 160, diff --git a/units/hoverdepthcharge.lua b/units/hoverdepthcharge.lua index cf18146b44..fc9b8c1313 100644 --- a/units/hoverdepthcharge.lua +++ b/units/hoverdepthcharge.lua @@ -88,6 +88,8 @@ return { hoverdepthcharge = { cegTag = [[torpedo_trail]], customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], burst = Shared.BURST_UNRELIABLE, }, @@ -166,6 +168,10 @@ return { hoverdepthcharge = { tracks = false, turnRate = 0, turret = true, + customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], + }, weaponType = [[Cannon]], weaponVelocity = 400, }, @@ -178,6 +184,8 @@ return { hoverdepthcharge = { craterMult = 0, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], bogus = 1, }, diff --git a/units/hoverheavyraid.lua b/units/hoverheavyraid.lua index 486c2a2fef..e8a4c073de 100644 --- a/units/hoverheavyraid.lua +++ b/units/hoverheavyraid.lua @@ -75,6 +75,8 @@ return { hoverheavyraid = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_slowbeam.png]], timeslow_damagefactor = 1.667, light_camera_height = 2000, light_color = [[0.85 0.33 1]], diff --git a/units/hoverminer.lua b/units/hoverminer.lua index 1d7cac89db..2a49fdddf7 100644 --- a/units/hoverminer.lua +++ b/units/hoverminer.lua @@ -70,6 +70,8 @@ return { hoverminer = { craterMult = 0, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], spawns_name = "wolverine_mine", spawns_expire = 60, }, diff --git a/units/hoverraid.lua b/units/hoverraid.lua index a0042a23cc..3e06d43a46 100644 --- a/units/hoverraid.lua +++ b/units/hoverraid.lua @@ -78,6 +78,8 @@ return { hoverraid = { craterMult = 0, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], burst = Shared.BURST_RELIABLE, single_hit = true, light_camera_height = 1200, diff --git a/units/hoverriot.lua b/units/hoverriot.lua index 0fcb48f981..30469f9941 100644 --- a/units/hoverriot.lua +++ b/units/hoverriot.lua @@ -83,6 +83,8 @@ return { hoverriot = { craterMult = 0, customparams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], stats_hide_damage = 1, -- continuous laser stats_hide_reload = 1, diff --git a/units/hovershotgun.lua b/units/hovershotgun.lua index 3921ab22e6..3e17924dc6 100644 --- a/units/hovershotgun.lua +++ b/units/hovershotgun.lua @@ -66,6 +66,8 @@ return { hovershotgun = { craterMult = 0, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], muzzleEffectFire = [[custom:HEAVY_CANNON_MUZZLE]], miscEffectFire = [[custom:RIOT_SHELL_L]], }, diff --git a/units/hoverskirm.lua b/units/hoverskirm.lua index b410c95ea2..05bd19772d 100644 --- a/units/hoverskirm.lua +++ b/units/hoverskirm.lua @@ -79,6 +79,8 @@ return { hoverskirm = { craterMult = 1.4, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], burst = Shared.BURST_RELIABLE, force_ignore_ground = [[1]], diff --git a/units/hoverskirm2.lua b/units/hoverskirm2.lua index 96bb3bfedc..f0412f0098 100644 --- a/units/hoverskirm2.lua +++ b/units/hoverskirm2.lua @@ -90,6 +90,10 @@ return { hoverskirm2 = { thickness = 4, tolerance = 8192, turret = true, + customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], + }, weaponType = [[BeamLaser]], }, diff --git a/units/hoversonic.lua b/units/hoversonic.lua index 7b3b27a1fa..122148d98c 100644 --- a/units/hoversonic.lua +++ b/units/hoversonic.lua @@ -67,6 +67,8 @@ return { hoversonic = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], slot = [[5]], muzzleEffectFire = [[custom:HEAVY_CANNON_MUZZLE]], miscEffectFire = [[custom:RIOT_SHELL_L]], diff --git a/units/jumpaa.lua b/units/jumpaa.lua index 144190e884..d041c448cf 100644 --- a/units/jumpaa.lua +++ b/units/jumpaa.lua @@ -82,6 +82,8 @@ return { jumpaa = { cylinderTargeting = 1, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], isaa = [[1]], light_camera_height = 1600, @@ -128,6 +130,8 @@ return { jumpaa = { cylinderTargeting = 1, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], isaa = [[1]], light_radius = 80, light_alpha = 0.08, diff --git a/units/jumparty.lua b/units/jumparty.lua index add3580cb5..d15fe3dddd 100644 --- a/units/jumparty.lua +++ b/units/jumparty.lua @@ -82,6 +82,8 @@ return { jumparty = { cegTag = [[flamer_cartoon]], customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], setunitsonfire = "1", burntime = 60, force_ignore_ground = [[1]], diff --git a/units/jumpassault.lua b/units/jumpassault.lua index ac6b8856f8..6cdd916c70 100644 --- a/units/jumpassault.lua +++ b/units/jumpassault.lua @@ -82,6 +82,8 @@ return { jumpassault = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], light_camera_height = 1000, light_color = [[1 1 0.7]], light_radius = 150, diff --git a/units/jumpblackhole.lua b/units/jumpblackhole.lua index 67cb6705ec..a74ab934b2 100644 --- a/units/jumpblackhole.lua +++ b/units/jumpblackhole.lua @@ -94,6 +94,8 @@ return { jumpblackhole = { craterMult = 2, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], falldamageimmunity = [[120]], area_damage = 1, diff --git a/units/jumpcon.lua b/units/jumpcon.lua index 3acc5f941a..e207f3dbd3 100644 --- a/units/jumpcon.lua +++ b/units/jumpcon.lua @@ -87,6 +87,8 @@ return { jumpcon = { craterMult = 0, customparams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_slowbeam.png]], timeslow_damagefactor = 12, timeslow_smartretarget = 0.33, timeslow_smartretargethealth = 50, diff --git a/units/jumpraid.lua b/units/jumpraid.lua index 0ad1aeea0a..fc80ddc95f 100644 --- a/units/jumpraid.lua +++ b/units/jumpraid.lua @@ -93,6 +93,8 @@ return { jumpraid = { cegTag = [[flamer_240_range]], customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], flamethrower = [[1]], setunitsonfire = "1", burnchance = "0.4", -- Per-impact diff --git a/units/jumpscout.lua b/units/jumpscout.lua index c9e387b742..6efd226f2f 100644 --- a/units/jumpscout.lua +++ b/units/jumpscout.lua @@ -91,6 +91,8 @@ return { jumpscout = { craterMult = 2, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], burst = Shared.BURST_RELIABLE, }, diff --git a/units/jumpskirm.lua b/units/jumpskirm.lua index 33424c9c06..e2320ee3b5 100644 --- a/units/jumpskirm.lua +++ b/units/jumpskirm.lua @@ -89,6 +89,8 @@ return { jumpskirm = { craterMult = 0, customparams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_slowbeam.png]], burst = Shared.BURST_RELIABLE, timeslow_damagefactor = 4, diff --git a/units/jumpsumo.lua b/units/jumpsumo.lua index cb71ca7aa0..59fc6ad2ce 100644 --- a/units/jumpsumo.lua +++ b/units/jumpsumo.lua @@ -129,6 +129,8 @@ return { jumpsumo = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], bogus = 1, }, @@ -176,6 +178,8 @@ return { jumpsumo = { craterMult = 0, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], impulse = [[-150]], light_color = [[0.33 0.33 1.28]], @@ -221,6 +225,8 @@ return { jumpsumo = { craterMult = 0, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], impulse = [[150]], light_color = [[0.85 0.2 0.2]], @@ -284,6 +290,8 @@ return { jumpsumo = { weaponVelocity = 5, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], hidden = true } }, diff --git a/units/mahlazer.lua b/units/mahlazer.lua index c8bd6e028f..dd15a13b6f 100644 --- a/units/mahlazer.lua +++ b/units/mahlazer.lua @@ -90,6 +90,8 @@ return { mahlazer = { coreThickness = 0.5, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], light_radius = 0, }, @@ -133,6 +135,8 @@ return { mahlazer = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], stats_damage = 24000, stats_hide_shield_damage = 1, light_radius = 0, @@ -180,6 +184,8 @@ return { mahlazer = { craterMult = 4, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], light_radius = 0, stats_hide_damage = 1, stats_hide_reload = 1, diff --git a/units/missileslow.lua b/units/missileslow.lua index f488219a59..b4fabc626d 100644 --- a/units/missileslow.lua +++ b/units/missileslow.lua @@ -64,6 +64,8 @@ return { missileslow = { collideFriendly = false, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_slowbeam.png]], timeslow_onlyslow = 1, light_color = [[0.6 0.22 0.8]], diff --git a/units/napalmmissile.lua b/units/napalmmissile.lua index 580efc86c3..94e05b9964 100644 --- a/units/napalmmissile.lua +++ b/units/napalmmissile.lua @@ -67,6 +67,8 @@ return { napalmmissile = { craterMult = 3.5, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], setunitsonfire = "1", burntime = 90, diff --git a/units/nebula.lua b/units/nebula.lua index f48d8d4929..9a7c8ee8ff 100644 --- a/units/nebula.lua +++ b/units/nebula.lua @@ -131,6 +131,10 @@ return { nebula = { stages = 12, tolerance = 5000, turret = true, + customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], + }, weaponType = [[Cannon]], weaponVelocity = 1200, }, diff --git a/units/planefighter.lua b/units/planefighter.lua index 42e6d362b8..8583ffaaa0 100644 --- a/units/planefighter.lua +++ b/units/planefighter.lua @@ -108,6 +108,8 @@ return { planefighter = { craterMult = 0, customparams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], light_camera_height = 1500, light_ground_height = 120, light_radius = 100, @@ -152,6 +154,8 @@ return { planefighter = { cylinderTargeting = 6, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], burst = Shared.BURST_RELIABLE, isaa = [[1]], diff --git a/units/planeheavyfighter.lua b/units/planeheavyfighter.lua index c482f75257..4cdd8392f7 100644 --- a/units/planeheavyfighter.lua +++ b/units/planeheavyfighter.lua @@ -91,6 +91,8 @@ return { planeheavyfighter = { cylinderTargeting = 1, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], isaa = [[1]], light_radius = 80, light_alpha = 0.08, diff --git a/units/pw_hq_attacker.lua b/units/pw_hq_attacker.lua index 2b27359c45..97685eb209 100644 --- a/units/pw_hq_attacker.lua +++ b/units/pw_hq_attacker.lua @@ -63,6 +63,10 @@ return { pw_hq_attacker = { reloadtime = 1, tolerance = 5000, turret = true, + customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], + }, weaponType = [[StarburstLauncher]], weaponVelocity = 500, }, diff --git a/units/raveparty.lua b/units/raveparty.lua index 842c04061e..50586acac1 100644 --- a/units/raveparty.lua +++ b/units/raveparty.lua @@ -115,6 +115,8 @@ return { raveparty = { craterMult = 3, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], script_reload = [[4]], reaim_time = 1, }, @@ -156,6 +158,8 @@ return { raveparty = { craterMult = 0.5, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], setunitsonfire = "1", burntime = 240, burnchance = 1, @@ -207,6 +211,8 @@ return { raveparty = { craterMult = 1, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], script_reload = [[4]], reaim_time = 1, }, @@ -249,6 +255,8 @@ return { raveparty = { craterMult = 1, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], gatherradius = [[540]], smoothradius = [[300]], smoothmult = [[0.9]], @@ -295,6 +303,8 @@ return { raveparty = { craterMult = 0.5, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], script_reload = [[4]], reaim_time = 1, }, @@ -342,6 +352,8 @@ return { raveparty = { craterMult = 0.5, customparams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_slowbeam.png]], timeslow_damagefactor = 10, nofriendlyfire = "needs hax", script_reload = [[4]], diff --git a/units/roost.lua b/units/roost.lua index f5129a2d93..311ebb0c6c 100644 --- a/units/roost.lua +++ b/units/roost.lua @@ -64,6 +64,8 @@ return { roost = { craterMult = 0, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], light_radius = 0, }, diff --git a/units/seismic.lua b/units/seismic.lua index aa65e9cdea..bee4d01c00 100644 --- a/units/seismic.lua +++ b/units/seismic.lua @@ -66,6 +66,8 @@ return { seismic = { craterMult = 1, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], gatherradius = [[416]], smoothradius = [[320]], detachmentradius = [[320]], diff --git a/units/shieldaa.lua b/units/shieldaa.lua index 7008c9acfd..c602ec849d 100644 --- a/units/shieldaa.lua +++ b/units/shieldaa.lua @@ -79,6 +79,8 @@ return { shieldaa = { cylinderTargeting = 1, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], burst = Shared.BURST_RELIABLE, isaa = [[1]], diff --git a/units/shieldarty.lua b/units/shieldarty.lua index b6b1aa0c42..98ee1b3aaa 100644 --- a/units/shieldarty.lua +++ b/units/shieldarty.lua @@ -68,6 +68,8 @@ return { shieldarty = { craterMult = 0, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], burst = Shared.BURST_RELIABLE, disarmDamageMult = 1, diff --git a/units/shieldassault.lua b/units/shieldassault.lua index 95b6962e53..aa57e03c32 100644 --- a/units/shieldassault.lua +++ b/units/shieldassault.lua @@ -112,6 +112,8 @@ return { shieldassault = { craterMult = 0, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], light_camera_height = 1400, light_color = [[0.80 0.54 0.23]], light_radius = 200, diff --git a/units/shieldfelon.lua b/units/shieldfelon.lua index 213b7be71b..e58ea61bdb 100644 --- a/units/shieldfelon.lua +++ b/units/shieldfelon.lua @@ -102,6 +102,8 @@ return { shieldfelon = { cylinderTargeting = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], shield_drain = 78, light_camera_height = 2500, diff --git a/units/shieldraid.lua b/units/shieldraid.lua index 5b72ebc21a..106d4bb7ca 100644 --- a/units/shieldraid.lua +++ b/units/shieldraid.lua @@ -74,6 +74,8 @@ return { shieldraid = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], light_camera_height = 1200, light_radius = 120, }, diff --git a/units/shieldriot.lua b/units/shieldriot.lua index 8a45e2e997..c4fcafdc63 100644 --- a/units/shieldriot.lua +++ b/units/shieldriot.lua @@ -96,6 +96,8 @@ return { shieldriot = { }, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_slowbeam.png]], light_radius = 0, lups_explodespeed = 1, lups_explodelife = 0.6, @@ -151,6 +153,10 @@ return { shieldriot = { turret = true, weaponAcceleration = 200, weaponTimer = 0.1, + customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], + }, weaponType = [[StarburstLauncher]], weaponVelocity = 200, }, @@ -188,6 +194,10 @@ return { shieldriot = { turret = true, weaponAcceleration = 200, weaponTimer = 0.1, + customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], + }, weaponType = [[StarburstLauncher]], weaponVelocity = 200, }, diff --git a/units/shieldscout.lua b/units/shieldscout.lua index beb3a9ae28..2e99c9c9f8 100644 --- a/units/shieldscout.lua +++ b/units/shieldscout.lua @@ -78,6 +78,8 @@ return { shieldscout = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], light_radius = 0, combatrange = 5, }, diff --git a/units/shieldskirm.lua b/units/shieldskirm.lua index 99bb4dd5e9..67b8286a5c 100644 --- a/units/shieldskirm.lua +++ b/units/shieldskirm.lua @@ -75,6 +75,8 @@ return { shieldskirm = { craterMult = 2, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], burst = Shared.BURST_RELIABLE, light_camera_height = 1800, diff --git a/units/shipaa.lua b/units/shipaa.lua index 854c5e10cc..d033632ef4 100644 --- a/units/shipaa.lua +++ b/units/shipaa.lua @@ -98,6 +98,8 @@ return { shipaa = { cylinderTargeting = 1, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], isaa = [[1]], light_camera_height = 2600, @@ -144,6 +146,8 @@ return { shipaa = { cylinderTargeting = 1, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], burst = Shared.BURST_RELIABLE, isaa = [[1]], diff --git a/units/shiparty.lua b/units/shiparty.lua index b2638d92ba..0302eb2753 100644 --- a/units/shiparty.lua +++ b/units/shiparty.lua @@ -77,6 +77,8 @@ return { shiparty = { craterMult = 2, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], burst = Shared.BURST_RELIABLE, }, diff --git a/units/shipassault.lua b/units/shipassault.lua index 3145c27970..5143240397 100644 --- a/units/shipassault.lua +++ b/units/shipassault.lua @@ -97,6 +97,8 @@ return { shipassault = { craterMult = 0, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], force_ignore_ground = [[1]], slot = [[5]], muzzleEffectFire = [[custom:HEAVY_CANNON_MUZZLE]], @@ -148,6 +150,8 @@ return { shipassault = { craterBoost = 1, craterMult = 2, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], combatRange = 265, }, damage = { diff --git a/units/shipcarrier.lua b/units/shipcarrier.lua index c0f4493533..db456ba531 100644 --- a/units/shipcarrier.lua +++ b/units/shipcarrier.lua @@ -93,6 +93,8 @@ return { shipcarrier = { craterMult = 0, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], burst = Shared.BURST_RELIABLE, combatrange = 950, @@ -168,6 +170,10 @@ return { shipcarrier = { turret = true, weaponAcceleration = 20000, weaponTimer = 0.5, + customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], + }, weaponType = [[StarburstLauncher]], weaponVelocity = 20000, }, diff --git a/units/shipheavyarty.lua b/units/shipheavyarty.lua index 9f316a502e..4b4ca95cb9 100644 --- a/units/shipheavyarty.lua +++ b/units/shipheavyarty.lua @@ -124,6 +124,10 @@ return { shipheavyarty = { sprayAngle = 768, tolerance = 4096, turret = true, + customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], + }, weaponType = [[Cannon]], weaponVelocity = 475, }, diff --git a/units/shipriot.lua b/units/shipriot.lua index 24a0410fbc..a8cb011f28 100644 --- a/units/shipriot.lua +++ b/units/shipriot.lua @@ -89,6 +89,8 @@ return { shipriot = { craterMult = 0, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], light_camera_height = 2000, light_color = [[0.3 0.3 0.05]], light_radius = 50, diff --git a/units/shipscout.lua b/units/shipscout.lua index 1bba7c7893..b6863ac932 100644 --- a/units/shipscout.lua +++ b/units/shipscout.lua @@ -73,6 +73,8 @@ return { shipscout = { cylinderTargeting = 1, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], disarmDamageMult = 5.0, disarmDamageOnly = 0, disarmTimer = 3, -- seconds diff --git a/units/shipskirm.lua b/units/shipskirm.lua index 752b5bc2f1..f9d747c9d8 100644 --- a/units/shipskirm.lua +++ b/units/shipskirm.lua @@ -83,6 +83,8 @@ return { shipskirm = { craterMult = 2, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], force_ignore_ground = [[1]], light_camera_height = 1800, }, diff --git a/units/shiptorpraider.lua b/units/shiptorpraider.lua index 2456d64b33..d9b5769a8d 100644 --- a/units/shiptorpraider.lua +++ b/units/shiptorpraider.lua @@ -79,6 +79,8 @@ return { shiptorpraider = { cegTag = [[torpedo_trail]], customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], burst = Shared.BURST_RELIABLE, stays_underwater = 1, diff --git a/units/slicer.lua b/units/slicer.lua index 75a7293a0b..e13d77d342 100644 --- a/units/slicer.lua +++ b/units/slicer.lua @@ -81,6 +81,8 @@ return { slicer = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], nofriendlyfire = 1, }, @@ -140,6 +142,10 @@ return { slicer = { turret = true, waterWeapon = true, weaponTimer = 0.1, + customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], + }, weaponType = [[Cannon]], weaponVelocity = 500, }, diff --git a/units/spideraa.lua b/units/spideraa.lua index afca2456f8..d7ed9d9dca 100644 --- a/units/spideraa.lua +++ b/units/spideraa.lua @@ -64,6 +64,8 @@ return { spideraa = { cylinderTargeting = 1, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], burst = Shared.BURST_RELIABLE, isaa = [[1]], diff --git a/units/spideranarchid.lua b/units/spideranarchid.lua index 6fff0cf858..3d8a575287 100644 --- a/units/spideranarchid.lua +++ b/units/spideranarchid.lua @@ -97,6 +97,10 @@ return { spideranarchid = { thickness = 2, tolerance = 5000, turret = true, + customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], + }, weaponType = [[BeamLaser]], weaponVelocity = 500, }, diff --git a/units/spiderantiheavy.lua b/units/spiderantiheavy.lua index 7492608fa9..c7617aeba5 100644 --- a/units/spiderantiheavy.lua +++ b/units/spiderantiheavy.lua @@ -69,6 +69,8 @@ return { spiderantiheavy = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], burst = Shared.BURST_RELIABLE, light_color = [[1.85 1.85 0.45]], light_radius = 300, diff --git a/units/spiderassault.lua b/units/spiderassault.lua index 0ec06bf3d2..95bfccb066 100644 --- a/units/spiderassault.lua +++ b/units/spiderassault.lua @@ -77,6 +77,8 @@ return { spiderassault = { craterMult = 0, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], light_camera_height = 1800, light_color = [[0.80 0.54 0.23]], light_radius = 200, diff --git a/units/spidercrabe.lua b/units/spidercrabe.lua index 858321142a..587188102e 100644 --- a/units/spidercrabe.lua +++ b/units/spidercrabe.lua @@ -86,6 +86,8 @@ return { spidercrabe = { craterMult = 0.5, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], force_ignore_ground = [[1]], light_color = [[1.5 1.13 0.6]], light_radius = 450, diff --git a/units/spideremp.lua b/units/spideremp.lua index 15f8954e52..13f407f3b8 100644 --- a/units/spideremp.lua +++ b/units/spideremp.lua @@ -76,6 +76,8 @@ return { spideremp = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], extra_damage = 400, force_ignore_ground = [[1]], overstun_time = 0.3, diff --git a/units/spiderriot.lua b/units/spiderriot.lua index 8fecc24d8a..a30b6d6da0 100644 --- a/units/spiderriot.lua +++ b/units/spiderriot.lua @@ -67,6 +67,8 @@ return { spiderriot = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], light_color = [[0.9 0.22 0.22]], light_radius = 80, }, diff --git a/units/spiderscout.lua b/units/spiderscout.lua index a050035d33..cddabb1061 100644 --- a/units/spiderscout.lua +++ b/units/spiderscout.lua @@ -81,6 +81,8 @@ return { spiderscout = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], light_color = [[0.8 0.8 0]], light_radius = 50, }, diff --git a/units/spiderskirm.lua b/units/spiderskirm.lua index 078461a344..60e06942ed 100644 --- a/units/spiderskirm.lua +++ b/units/spiderskirm.lua @@ -63,6 +63,8 @@ return { spiderskirm = { craterMult = 0, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], light_camera_height = 2500, light_color = [[0.90 0.65 0.30]], light_radius = 180, diff --git a/units/starlight_satellite.lua b/units/starlight_satellite.lua index cabdec4210..c49e931626 100644 --- a/units/starlight_satellite.lua +++ b/units/starlight_satellite.lua @@ -78,6 +78,8 @@ return { starlight_satellite = { craterMult = 14, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], light_color = [[5 0.3 6]], light_radius = 2000, light_beam_start = 0.8, @@ -132,6 +134,8 @@ return { starlight_satellite = { craterMult = 30, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], light_color = [[3 0.2 4]], light_radius = 1200, light_beam_start = 0.8, @@ -178,6 +182,8 @@ return { starlight_satellite = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], light_color = [[3 0.2 4]], light_radius = 1200, light_beam_start = 0.8, diff --git a/units/staticantinuke.lua b/units/staticantinuke.lua index 019f968e7f..889410a0c2 100644 --- a/units/staticantinuke.lua +++ b/units/staticantinuke.lua @@ -78,6 +78,8 @@ return { staticantinuke = { craterMult = 2, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], reaim_time = 15, nuke_coverage = 2500, }, diff --git a/units/staticarty.lua b/units/staticarty.lua index 0f5ea81764..13a4723c78 100644 --- a/units/staticarty.lua +++ b/units/staticarty.lua @@ -79,6 +79,8 @@ return { staticarty = { craterMult = 2, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], light_color = [[1.4 0.8 0.3]], }, diff --git a/units/staticheavyarty.lua b/units/staticheavyarty.lua index 0f136c8b07..961ee04b1c 100644 --- a/units/staticheavyarty.lua +++ b/units/staticheavyarty.lua @@ -81,6 +81,8 @@ return { staticheavyarty = { craterMult = 0.5, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], gatherradius = [[240]], smoothradius = [[120]], smoothmult = [[0.5]], diff --git a/units/staticjammer.lua b/units/staticjammer.lua index 2ebddf7496..29db8dc151 100644 --- a/units/staticjammer.lua +++ b/units/staticjammer.lua @@ -80,6 +80,8 @@ return { staticjammer = { commandfire = true, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], gui_draw_range = 200, gui_draw_leashed_to_range = 1, attack_aoe_circle_mode = "cloaker" diff --git a/units/staticnuke.lua b/units/staticnuke.lua index 263d28fadd..f45234c3ca 100644 --- a/units/staticnuke.lua +++ b/units/staticnuke.lua @@ -65,6 +65,8 @@ return { staticnuke = { craterMult = 6, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], light_color = [[2.92 2.64 1.76]], light_radius = 3000, }, diff --git a/units/statictele.lua b/units/statictele.lua index 7a2017a134..30e7891a4d 100644 --- a/units/statictele.lua +++ b/units/statictele.lua @@ -59,6 +59,8 @@ return { statictele = { commandfire = true, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], attack_aoe_circle_mode = "cloaker" }, diff --git a/units/striderantiheavy.lua b/units/striderantiheavy.lua index 9cf926eedc..02593f99df 100644 --- a/units/striderantiheavy.lua +++ b/units/striderantiheavy.lua @@ -86,6 +86,8 @@ return { striderantiheavy = { craterMult = 6, customparams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], noexplode_speed_damage = 1, burst = Shared.BURST_UNRELIABLE, stats_burst_damage = 18000, diff --git a/units/striderarty.lua b/units/striderarty.lua index 1fa73912ea..721256aaba 100644 --- a/units/striderarty.lua +++ b/units/striderarty.lua @@ -73,6 +73,8 @@ return { striderarty = { craterMult = 2, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], force_ignore_ground = [[1]], light_camera_height = 2500, light_color = [[0.35 0.17 0.04]], diff --git a/units/striderbantha.lua b/units/striderbantha.lua index 12fc6dbed1..a95f1298b1 100644 --- a/units/striderbantha.lua +++ b/units/striderbantha.lua @@ -99,6 +99,8 @@ return { striderbantha = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], burst = Shared.BURST_RELIABLE, light_color = [[1.25 0.8 1.75]], @@ -150,6 +152,8 @@ return { striderbantha = { craterMult = 0, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], combatrange = 900, light_color = [[0.65 0.65 0.18]], light_radius = 380, @@ -199,6 +203,8 @@ return { striderbantha = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], extra_damage = 960, light_camera_height = 2200, diff --git a/units/striderdante.lua b/units/striderdante.lua index 5d457622fd..72d745bd07 100644 --- a/units/striderdante.lua +++ b/units/striderdante.lua @@ -104,6 +104,8 @@ return { striderdante = { cegTag = [[flamer_320_range]], customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], flamethrower = [[1]], setunitsonfire = "1", burnchance = "0.4", -- Per-impact @@ -157,6 +159,8 @@ return { striderdante = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], light_camera_height = 1500, light_color = [[0.9 0.4 0.12]], light_radius = 90, @@ -209,6 +213,8 @@ return { striderdante = { craterMult = 0, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], area_damage = 1, area_damage_radius = 78, area_damage_dps = 17, @@ -267,6 +273,8 @@ return { striderdante = { craterMult = 0, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], area_damage = 1, area_damage_radius = 78, area_damage_dps = 17, diff --git a/units/striderdetriment.lua b/units/striderdetriment.lua index 56f2583cd8..7418d0a682 100644 --- a/units/striderdetriment.lua +++ b/units/striderdetriment.lua @@ -150,6 +150,8 @@ return { striderdetriment = { craterMult = 0, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], single_hit_multi = true, reaim_time = 1, }, @@ -199,6 +201,8 @@ return { striderdetriment = { cylinderTargeting = 1, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], isaa = [[1]], reaim_time = 1, light_radius = 80, @@ -239,6 +243,8 @@ return { striderdetriment = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], light_color = [[0.2 0.8 0.2]], reaim_time = 1, }, @@ -295,6 +301,8 @@ return { striderdetriment = { commandFire = true, customparams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], light_radius = 380, light_color = [[0.5 0.95 0]], gatherradius = [[192]], @@ -367,6 +375,8 @@ return { striderdetriment = { weaponVelocity = 5, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], hidden = true } }, @@ -427,6 +437,8 @@ return { striderdetriment = { weaponVelocity = 5, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], hidden = true } }, diff --git a/units/striderdozer.lua b/units/striderdozer.lua index a284e53ae3..b1c25c3fec 100644 --- a/units/striderdozer.lua +++ b/units/striderdozer.lua @@ -81,6 +81,8 @@ return { striderdozer = { burstrate = 0.2, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], gatherradius = [[180]], smoothradius = [[200]], detachmentradius = [[200]], diff --git a/units/striderscorpion.lua b/units/striderscorpion.lua index d29187fb37..a62c59c0cb 100644 --- a/units/striderscorpion.lua +++ b/units/striderscorpion.lua @@ -119,6 +119,8 @@ return { striderscorpion = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], bogus = 1, reaim_time = 1, }, @@ -162,6 +164,8 @@ return { striderscorpion = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], extra_damage = 1080, light_camera_height = 1600, @@ -211,6 +215,8 @@ return { striderscorpion = { cylinderTargeting = 0, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], burst = Shared.BURST_UNRELIABLE, light_color = [[0.7 0.7 0.2]], light_radius = 320, @@ -257,6 +263,8 @@ return { striderscorpion = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], light_color = [[0.9 0.22 0.22]], light_radius = 80, reaim_time = 1, diff --git a/units/subraider.lua b/units/subraider.lua index 3e7e6903dc..03b4805d8a 100644 --- a/units/subraider.lua +++ b/units/subraider.lua @@ -82,6 +82,8 @@ return { subraider = { cegTag = [[torptrailpurple]], customparams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_slowbeam.png]], burst = Shared.BURST_RELIABLE, timeslow_damagefactor = 2, @@ -133,6 +135,8 @@ return { subraider = { craterMult = 0, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], bogus = 1, }, diff --git a/units/subtacmissile.lua b/units/subtacmissile.lua index d6fe283bad..e7702dab7e 100644 --- a/units/subtacmissile.lua +++ b/units/subtacmissile.lua @@ -69,6 +69,8 @@ return { subtacmissile = { craterMult = 3.5, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], burst = Shared.BURST_RELIABLE, }, diff --git a/units/tacnuke.lua b/units/tacnuke.lua index 5dfbd9fc66..7b023ed1da 100644 --- a/units/tacnuke.lua +++ b/units/tacnuke.lua @@ -66,6 +66,8 @@ return { tacnuke = { craterMult = 3.5, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], burst = Shared.BURST_RELIABLE, lups_explodelife = 1.5, diff --git a/units/tankaa.lua b/units/tankaa.lua index f5b871c53a..b26ec7fe89 100644 --- a/units/tankaa.lua +++ b/units/tankaa.lua @@ -83,6 +83,8 @@ return { tankaa = { cylinderTargeting = 1, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], reaim_time = 1, -- looks silly when rotating otherwise (high turret and body turn rates) isaa = [[1]], light_radius = 0, diff --git a/units/tankarty.lua b/units/tankarty.lua index 264462fc3f..03d4b8ab69 100644 --- a/units/tankarty.lua +++ b/units/tankarty.lua @@ -74,6 +74,8 @@ return { tankarty = { craterMult = 2, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], burst = Shared.BURST_RELIABLE, light_color = [[1.4 0.8 0.3]], diff --git a/units/tankassault.lua b/units/tankassault.lua index 098470ab25..0eb55864d0 100644 --- a/units/tankassault.lua +++ b/units/tankassault.lua @@ -85,6 +85,8 @@ return { tankassault = { craterMult = 0, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], burst = Shared.BURST_RELIABLE, }, diff --git a/units/tankcon.lua b/units/tankcon.lua index 39e0592851..40d7fbe168 100644 --- a/units/tankcon.lua +++ b/units/tankcon.lua @@ -90,6 +90,8 @@ return { tankcon = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], light_camera_height = 1200, light_radius = 120, }, diff --git a/units/tankheavyarty.lua b/units/tankheavyarty.lua index 682ad5326f..6b2b54af91 100644 --- a/units/tankheavyarty.lua +++ b/units/tankheavyarty.lua @@ -85,6 +85,8 @@ return { tankheavyarty = { craterMult = 0, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], reaim_time = 15, -- Some sort of bug prevents firing. gatherradius = [[240]], diff --git a/units/tankheavyassault.lua b/units/tankheavyassault.lua index 8af8ed7e2d..dc57975a84 100644 --- a/units/tankheavyassault.lua +++ b/units/tankheavyassault.lua @@ -84,6 +84,8 @@ return { tankheavyassault = { craterMult = 0, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], burst = Shared.BURST_RELIABLE, gatherradius = [[105]], smoothradius = [[70]], @@ -123,6 +125,8 @@ return { tankheavyassault = { craterMult = 0, customparams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_slowbeam.png]], timeslow_onlyslow = 1, timeslow_smartretarget = 0.33, diff --git a/units/tankheavyraid.lua b/units/tankheavyraid.lua index 458636d78c..d1c37a7eef 100644 --- a/units/tankheavyraid.lua +++ b/units/tankheavyraid.lua @@ -79,6 +79,8 @@ return { tankheavyraid = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], burst = Shared.BURST_RELIABLE, extra_damage = 500, light_camera_height = 1600, diff --git a/units/tankraid.lua b/units/tankraid.lua index 1b5068833c..a7c4c29d5f 100644 --- a/units/tankraid.lua +++ b/units/tankraid.lua @@ -90,6 +90,8 @@ return { tankraid = { craterMult = 0, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], setunitsonfire = "1", burnchance = "1", burntime = 30, diff --git a/units/tankriot.lua b/units/tankriot.lua index 0728e00979..b8fde5433f 100644 --- a/units/tankriot.lua +++ b/units/tankriot.lua @@ -77,6 +77,8 @@ return { tankriot = { craterMult = 2, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], burst = Shared.BURST_RELIABLE, gatherradius = [[120]], diff --git a/units/tele_beacon.lua b/units/tele_beacon.lua index fb216b4011..941bbd4dde 100644 --- a/units/tele_beacon.lua +++ b/units/tele_beacon.lua @@ -6,8 +6,9 @@ return { tele_beacon = { category = [[SINK UNARMED]], customParams = { - dontcount = [[1]], - normaltex = [[unittextures/b_spy_jammer_radar_termite_normals.dds]], + dontcount = [[1]], + teleporter_is_beacon = true, + normaltex = [[unittextures/b_spy_jammer_radar_termite_normals.dds]], }, explodeAs = [[TINY_BUILDINGEX]], diff --git a/units/turretaaclose.lua b/units/turretaaclose.lua index 152d4e504c..42915ea7b6 100644 --- a/units/turretaaclose.lua +++ b/units/turretaaclose.lua @@ -72,6 +72,8 @@ return { turretaaclose = { cylinderTargeting = 3, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], burst = Shared.BURST_RELIABLE, isaa = [[1]], diff --git a/units/turretaafar.lua b/units/turretaafar.lua index 04df4c6847..f4c2bebc4e 100644 --- a/units/turretaafar.lua +++ b/units/turretaafar.lua @@ -75,6 +75,8 @@ return { turretaafar = { cylinderTargeting = 1, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], isaa = [[1]], light_color = [[0.6 0.7 0.7]], light_radius = 420, diff --git a/units/turretaaflak.lua b/units/turretaaflak.lua index 1fbbfd04ef..66cc5924b2 100644 --- a/units/turretaaflak.lua +++ b/units/turretaaflak.lua @@ -73,6 +73,8 @@ return { turretaaflak = { cylinderTargeting = 1, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], isaa = [[1]], light_radius = 0, }, diff --git a/units/turretaaheavy.lua b/units/turretaaheavy.lua index 25fa9e8081..5312de9112 100644 --- a/units/turretaaheavy.lua +++ b/units/turretaaheavy.lua @@ -64,6 +64,8 @@ return { turretaaheavy = { cylinderTargeting = 3.2, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], isaa = [[1]], radar_homing_distance = 1800, diff --git a/units/turretaalaser.lua b/units/turretaalaser.lua index d1d9e5ed75..8ef576b8e6 100644 --- a/units/turretaalaser.lua +++ b/units/turretaalaser.lua @@ -77,6 +77,8 @@ return { turretaalaser = { cylinderTargeting = 1, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], isaa = [[1]], light_camera_height = 2600, diff --git a/units/turretantiheavy.lua b/units/turretantiheavy.lua index 67110c32b7..034a2d4dab 100644 --- a/units/turretantiheavy.lua +++ b/units/turretantiheavy.lua @@ -75,6 +75,8 @@ return { turretantiheavy = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], burst = Shared.BURST_RELIABLE, light_color = [[1.6 1.05 2.25]], diff --git a/units/turretemp.lua b/units/turretemp.lua index 0c91c7b0a5..f5e5eb8779 100644 --- a/units/turretemp.lua +++ b/units/turretemp.lua @@ -74,6 +74,8 @@ return { turretemp = { cylinderTargeting = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_lightninggun.png]], light_color = [[0.75 0.75 0.56]], light_radius = 220, }, diff --git a/units/turretgauss.lua b/units/turretgauss.lua index d9d8d8c8a5..c130feebb7 100644 --- a/units/turretgauss.lua +++ b/units/turretgauss.lua @@ -76,6 +76,8 @@ return { turretgauss = { craterMult = 0, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], burst = Shared.BURST_RELIABLE, single_hit = true, diff --git a/units/turretheavy.lua b/units/turretheavy.lua index fc9a85e5f1..df7d3e3b37 100644 --- a/units/turretheavy.lua +++ b/units/turretheavy.lua @@ -90,6 +90,8 @@ return { turretheavy = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], light_camera_height = 2000, light_color = [[0.9 0.4 0.12]], light_radius = 180, @@ -142,6 +144,8 @@ return { turretheavy = { craterMult = 1.2, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], light_color = [[2.2 1.6 0.9]], light_radius = 550, }, diff --git a/units/turretheavylaser.lua b/units/turretheavylaser.lua index 69af436b25..0879aa0582 100644 --- a/units/turretheavylaser.lua +++ b/units/turretheavylaser.lua @@ -75,6 +75,8 @@ return { turretheavylaser = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], burst = Shared.BURST_UNRELIABLE, prevent_overshoot_fudge = 15, diff --git a/units/turretimpulse.lua b/units/turretimpulse.lua index b75aa68518..39cd55d082 100644 --- a/units/turretimpulse.lua +++ b/units/turretimpulse.lua @@ -75,6 +75,8 @@ return { turretimpulse = { craterMult = 0, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], impulse = [[-150]], light_color = [[0.33 0.33 1.28]], @@ -120,6 +122,8 @@ return { turretimpulse = { craterMult = 0, customParams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], impulse = [[150]], light_color = [[0.85 0.2 0.2]], diff --git a/units/turretlaser.lua b/units/turretlaser.lua index 1d5aaf02ea..fc0995ff7d 100644 --- a/units/turretlaser.lua +++ b/units/turretlaser.lua @@ -75,6 +75,8 @@ return { turretlaser = { craterMult = 0, customparams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], stats_hide_damage = 1, -- continuous laser stats_hide_reload = 1, prevent_overshoot_fudge = 15, diff --git a/units/turretmissile.lua b/units/turretmissile.lua index bcf029869a..2e33622102 100644 --- a/units/turretmissile.lua +++ b/units/turretmissile.lua @@ -76,6 +76,8 @@ return { turretmissile = { cylinderTargeting = 5, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], burst = Shared.BURST_RELIABLE, isaa = [[1]], diff --git a/units/turretriot.lua b/units/turretriot.lua index 0c960a9175..bb7d405e7c 100644 --- a/units/turretriot.lua +++ b/units/turretriot.lua @@ -80,6 +80,8 @@ return { turretriot = { craterMult = 0.3, customparams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_heavymachinegun.png]], light_color = [[0.8 0.76 0.38]], light_radius = 180, proximity_priority = 5, -- Don't use this unless required as it causes O(N^2) seperation checks per slow update. diff --git a/units/turretsunlance.lua b/units/turretsunlance.lua index d97c310b49..648a05277c 100644 --- a/units/turretsunlance.lua +++ b/units/turretsunlance.lua @@ -70,6 +70,8 @@ return { turretsunlance = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_slowbeam.png]], timeslow_damage = [[2500]], }, diff --git a/units/turrettorp.lua b/units/turrettorp.lua index 60bea4277a..ea8c185ac9 100644 --- a/units/turrettorp.lua +++ b/units/turrettorp.lua @@ -63,6 +63,8 @@ return { turrettorp = { cegTag = [[torpedo_trail]], customparams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], stays_underwater = 1, }, diff --git a/units/vehaa.lua b/units/vehaa.lua index 343a51c3f1..4b4bbd8258 100644 --- a/units/vehaa.lua +++ b/units/vehaa.lua @@ -84,6 +84,8 @@ return { vehaa = { cylinderTargeting = 1, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], burst = Shared.BURST_RELIABLE, isaa = [[1]], diff --git a/units/veharty.lua b/units/veharty.lua index 3bc732d7ad..8ad8046d87 100644 --- a/units/veharty.lua +++ b/units/veharty.lua @@ -81,6 +81,8 @@ return { veharty = { craterMult = 0, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], damage_vs_shield = [[190]], damage_vs_feature = [[190]], force_ignore_ground = [[1]], diff --git a/units/vehassault.lua b/units/vehassault.lua index 4fba1e72bd..8f2724115f 100644 --- a/units/vehassault.lua +++ b/units/vehassault.lua @@ -83,6 +83,8 @@ return { vehassault = { craterMult = 0, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], light_camera_height = 1500, burst = Shared.BURST_RELIABLE, }, diff --git a/units/vehcapture.lua b/units/vehcapture.lua index 667bd0c375..569cfbfe43 100644 --- a/units/vehcapture.lua +++ b/units/vehcapture.lua @@ -75,6 +75,8 @@ return { vehcapture = { craterMult = 0, customparams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], capture_scaling = 1, is_capture = 1, post_capture_reload = 360, diff --git a/units/vehheavyarty.lua b/units/vehheavyarty.lua index 88fea787df..6ec69a8ccc 100644 --- a/units/vehheavyarty.lua +++ b/units/vehheavyarty.lua @@ -73,6 +73,8 @@ return { vehheavyarty = { craterMult = 2, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], burst = Shared.BURST_RELIABLE, reaim_time = 15, -- Some script bug. It does not need fast aim updates anyway. light_camera_height = 2500, diff --git a/units/vehraid.lua b/units/vehraid.lua index 8d970681d4..1a65304773 100644 --- a/units/vehraid.lua +++ b/units/vehraid.lua @@ -81,6 +81,8 @@ return { vehraid = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], light_camera_height = 1500, light_color = [[0.9 0.4 0.12]], light_radius = 120, diff --git a/units/vehriot.lua b/units/vehriot.lua index e24145ef49..00f81ba73e 100644 --- a/units/vehriot.lua +++ b/units/vehriot.lua @@ -85,6 +85,8 @@ return { vehriot = { craterMult = 0.5, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], gatherradius = [[90]], smoothradius = [[60]], smoothmult = [[0.08]], diff --git a/units/vehscout.lua b/units/vehscout.lua index f2ac842088..87f9200160 100644 --- a/units/vehscout.lua +++ b/units/vehscout.lua @@ -74,6 +74,8 @@ return { vehscout = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_slowbeam.png]], timeslow_damagefactor = 4, light_camera_height = 2000, diff --git a/units/vehsupport.lua b/units/vehsupport.lua index af9a9eb9b6..b0bef83478 100644 --- a/units/vehsupport.lua +++ b/units/vehsupport.lua @@ -88,6 +88,8 @@ return { vehsupport = { craterMult = 0, customParams = { + weapon_class = "explosive", + icon = [[unitpics/commweapon_missilelauncher.png]], light_camera_height = 2000, light_radius = 200, }, diff --git a/units/wolverine_mine.lua b/units/wolverine_mine.lua index 4296252c08..54a443901b 100644 --- a/units/wolverine_mine.lua +++ b/units/wolverine_mine.lua @@ -73,6 +73,8 @@ return { wolverine_mine = { craterMult = 0, customparams = { + weapon_class = "burst", + icon = [[unitpics/commweapon_shotgun.png]], stats_hide_dps = 1, -- one use stats_hide_reload = 1, diff --git a/units/zenith.lua b/units/zenith.lua index 8789cda883..0447d013b0 100644 --- a/units/zenith.lua +++ b/units/zenith.lua @@ -74,6 +74,8 @@ return { zenith = { craterMult = 0, customParams = { + weapon_class = "energy", + icon = [[unitpics/commweapon_beamlaser.png]], light_radius = 0, }, @@ -119,6 +121,8 @@ return { zenith = { craterMult = 6, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], light_color = [[2.4 1.5 0.6]], light_radius = 600, @@ -181,6 +185,8 @@ return { zenith = { craterMult = 6, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], light_radius = 0, spawns_name = "asteroid_dead", @@ -241,6 +247,8 @@ return { zenith = { craterMult = 6, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], light_radius = 0, spawns_name = "asteroid_dead", @@ -302,6 +310,8 @@ return { zenith = { craterMult = 6, customParams = { + weapon_class = "kinetic", + icon = [[unitpics/commweapon_gaussrifle.png]], light_color = [[2.4 1.5 0.6]], light_radius = 600,