Skip to content

Commit

Permalink
Shields - Dgun compatibility fix (#4113)
Browse files Browse the repository at this point in the history

somewhere along the development path dguns stopped interacting with shields correctly - and they never worked consistently. This PR fixes that and adds the DGun to the bitmask that can be stopped by the extra units t3 shields.

Unchanged:

    Vanilla t2 shields don't block dgun

Changed:

    dgun projectile now sits in the same spot as it grinds against a shield, rather than jumping back arbitrary distance like before. looks better and applies damage more consistently.
    now works whether rework enabled or not consistently
    t3 shields now block dgun (but it eats through them super fast)

Other:

    GG.AddShieldDamage never worked due to being nested inside of a callin. I put it outside the callin and it now works. Ended up not using it, though.

Tested both vanilla modoptions and shield rework enabled in a crazy coop vs scavs game to check for crashes
  • Loading branch information
SethDGamre authored Jan 4, 2025
1 parent d5430cc commit 09bdb66
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 90 deletions.
103 changes: 50 additions & 53 deletions luarules/gadgets/unit_dgun_behaviour.lua
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,26 @@ end
local spSetProjectilePosition = Spring.SetProjectilePosition
local spSetProjectileVelocity = Spring.SetProjectileVelocity
local spGetProjectilePosition = Spring.GetProjectilePosition
local spGetProjectileDefID = Spring.GetProjectileDefID
local spGetUnitShieldState = Spring.GetUnitShieldState
local spGetProjectileVelocity = Spring.GetProjectileVelocity
local spSetUnitShieldState = Spring.SetUnitShieldState
local spGetGroundHeight = Spring.GetGroundHeight
local spDeleteProjectile = Spring.DeleteProjectile
local spGetProjectileOwnerID = Spring.GetProjectileOwnerID
local spSpawnExplosion = Spring.SpawnExplosion
local spGetUnitPosition = Spring.GetUnitPosition
local spSpawnCEG = Spring.SpawnCEG
local spGetGameFrame = Spring.GetGameFrame

local modOptions = Spring.GetModOptions()
local dgunWeaponsTTL = {}
local dgunWeapons = {}
local dgunData = {}
local dgunDef = {}
local dgunTimeouts = {}
local dgunShieldPenetrations = {}

for weaponDefID, weaponDef in ipairs(WeaponDefs) do
if weaponDef.type == 'DGun' then
Script.SetWatchProjectile(weaponDefID, true)
dgunWeapons[weaponDefID] = weaponDef
dgunWeaponsTTL[weaponDefID] = weaponDef.range / weaponDef.projectilespeed
dgunDef[weaponDefID] = weaponDef
dgunDef[weaponDefID].ttl = weaponDef.range / weaponDef.projectilespeed
dgunDef[weaponDefID].setback = weaponDef.projectilespeed
end
end

Expand All @@ -63,39 +61,42 @@ local flyingDGuns = {}
local groundedDGuns = {}

local function addVolumetricDamage(projectileID)
local weaponDefID = spGetProjectileDefID(projectileID)
local ownerID = spGetProjectileOwnerID(projectileID)
local x,y,z =spGetProjectilePosition(projectileID)
local explosionParame ={
local projectileData = dgunData[projectileID]
local weaponDefID = projectileData.weaponDefID
local x, y, z = spGetProjectilePosition(projectileID)
local explosionParame = {
weaponDef = weaponDefID,
owner = ownerID,
owner = projectileData.proOwnerID,
projectileID = projectileID,
damages = dgunWeapons[weaponDefID].damages,
damages = dgunDef[weaponDefID].damages,
hitUnit = 1,
hitFeature = 1,
craterAreaOfEffect = dgunWeapons[weaponDefID].craterAreaOfEffect,
damageAreaOfEffect = dgunWeapons[weaponDefID].damageAreaOfEffect,
edgeEffectiveness = dgunWeapons[weaponDefID].edgeEffectiveness,
explosionSpeed = dgunWeapons[weaponDefID].explosionSpeed,
impactOnly = dgunWeapons[weaponDefID].impactOnly,
ignoreOwner = dgunWeapons[weaponDefID].noSelfDamage,
craterAreaOfEffect = dgunDef[weaponDefID].craterAreaOfEffect,
damageAreaOfEffect = dgunDef[weaponDefID].damageAreaOfEffect,
edgeEffectiveness = dgunDef[weaponDefID].edgeEffectiveness,
explosionSpeed = dgunDef[weaponDefID].explosionSpeed,
impactOnly = dgunDef[weaponDefID].impactOnly,
ignoreOwner = dgunDef[weaponDefID].noSelfDamage,
damageGround = true,
}

spSpawnExplosion(x, y ,z, 0, 0, 0, explosionParame)
spSpawnExplosion(x, y, z, 0, 0, 0, explosionParame)
end

function gadget:ProjectileCreated(proID, proOwnerID, weaponDefID)
if dgunWeapons[weaponDefID] then
if dgunDef[weaponDefID] then
dgunData[proID] = { proOwnerID = proOwnerID, weaponDefID = weaponDefID }
flyingDGuns[proID] = true
dgunTimeouts[proID] = (spGetGameFrame() + dgunWeaponsTTL[weaponDefID])
dgunTimeouts[proID] = (spGetGameFrame() + dgunDef[weaponDefID].ttl)
end
end

function gadget:ProjectileDestroyed(proID)
flyingDGuns[proID] = nil
groundedDGuns[proID] = nil
dgunTimeouts[proID] = nil
dgunShieldPenetrations[proID] = nil
dgunData[proID] = nil
end

function gadget:GameFrame(frame)
Expand All @@ -110,7 +111,7 @@ function gadget:GameFrame(frame)
if y < h + 1 or y < 0 then -- assume ground or water collision
-- normalize horizontal velocity
local dx, _, dz, speed = spGetProjectileVelocity(proID)
local norm = speed / math.sqrt(dx^2 + dz^2)
local norm = speed / math.sqrt(dx ^ 2 + dz ^ 2)
local ndx = dx * norm
local ndz = dz * norm
spSetProjectileVelocity(proID, ndx, 0, ndz)
Expand Down Expand Up @@ -140,58 +141,54 @@ function gadget:GameFrame(frame)
end
end

function gadget:UnitPreDamaged(unitID, unitDefID, unitTeam, damage, paralyzer, weaponDefID, projectileID, attackerID, attackerDefID, attackerTeam)
if dgunWeapons[weaponDefID] and isCommander[attackerDefID] and (isCommander[unitDefID] or isDecoyCommander[unitDefID]) then
function gadget:UnitPreDamaged(unitID, unitDefID, unitTeam, damage, paralyzer, weaponDefID, projectileID, attackerID,
attackerDefID, attackerTeam)
if dgunDef[weaponDefID] and isCommander[attackerDefID] and (isCommander[unitDefID] or isDecoyCommander[unitDefID]) then
if isDecoyCommander[unitDefID] then
return dgunWeapons[weaponDefID].damages[0]
return dgunDef[weaponDefID].damages[0]
else
spDeleteProjectile(projectileID)
local x, y, z = spGetUnitPosition(unitID)
spSpawnCEG("dgun-deflect", x, y, z, 0, 0, 0, 0, 0)
local armorClass = UnitDefs[unitDefID].armorType
return dgunWeapons[weaponDefID].damages[armorClass]
return dgunDef[weaponDefID].damages[armorClass]
end
end

return damage
end

local lastShieldFrameCheck = {}

function gadget:ShieldPreDamaged(proID, proOwnerID, shieldEmitterWeaponNum, shieldCarrierUnitID, bounceProjectile, beamEmitterWeaponNum, beamEmitterUnitID, startX, startY, startZ, hitX, hitY, hitZ)
function gadget:ShieldPreDamaged(proID, proOwnerID, shieldEmitterWeaponNum, shieldCarrierUnitID, bounceProjectile,
beamEmitterWeaponNum, beamEmitterUnitID, startX, startY, startZ, hitX, hitY, hitZ)
if proID > -1 and dgunTimeouts[proID] then
local proDefID = spGetProjectileDefID(proID)
if dgunShieldPenetrations[proID] then return true end
local proDefID = dgunData[proID].weaponDefID
local shieldEnabledState, shieldPower = spGetUnitShieldState(shieldCarrierUnitID)
local damage = WeaponDefs[proDefID].damages[Game.armorTypes.shields] or WeaponDefs[proDefID].damages[Game.armorTypes.default]

local gameframe = spGetGameFrame()

if not modOptions.shieldsrework then
local damageCooldownFrames = 10
if shieldPower < damage then return false end
local damage = WeaponDefs[proDefID].damages[Game.armorTypes.shields] or
WeaponDefs[proDefID].damages[Game.armorTypes.default]

lastShieldFrameCheck[shieldCarrierUnitID] = lastShieldFrameCheck[shieldCarrierUnitID] or gameframe
local weaponDefID = dgunData[proID].weaponDefID

if hitX > 0 and lastShieldFrameCheck[shieldCarrierUnitID] <= gameframe then
shieldPower = math.max(shieldPower - damage, 0)
spSetUnitShieldState(shieldCarrierUnitID, shieldEmitterWeaponNum, shieldEnabledState, shieldPower)
lastShieldFrameCheck[shieldCarrierUnitID] = gameframe + damageCooldownFrames
if not dgunShieldPenetrations[proID] then
if shieldPower <= damage then
shieldPower = 0
dgunShieldPenetrations[proID] = true
end
end

-- Engine does not provide a way for shields to stop DGun projectiles, they will impact once and carry on through,
-- need to manually move them back a bit so the next touchdown hits the shield
if shieldPower > 0 then
-- Extra offset required to avoid edgecase where projectile penetrates, value chosen empirically
local offsetFactor = 1.1
local dx, dy, dz, speed = spGetProjectileVelocity(proID)
local magnitude = math.sqrt(dx^2 + dy^2 + dz^2)
if not dgunShieldPenetrations[proID] then
-- Adjusting the projectile position based on setback
local dx, dy, dz = spGetProjectileVelocity(proID)
local magnitude = math.sqrt(dx ^ 2 + dy ^ 2 + dz ^ 2)
local normalX, normalY, normalZ = dx / magnitude, dy / magnitude, dz / magnitude

local setback = dgunDef[weaponDefID].setback

local x, y, z = spGetProjectilePosition(proID)
local newX = x - speed * normalX * offsetFactor
local newY = y - speed * normalY * offsetFactor
local newZ = z - speed * normalZ * offsetFactor
local newX = x - normalX * setback
local newY = y - normalY * setback
local newZ = z - normalZ * setback

spSetProjectilePosition(proID, newX, newY, newZ)
end
Expand Down
69 changes: 32 additions & 37 deletions luarules/gadgets/unit_shield_behaviour.lua
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,6 @@ function gadget:ShieldPreDamaged(proID, proOwnerID, shieldWeaponNum, shieldUnitI
weaponDefID = projectileDefIDCache[proID] or spGetProjectileDefID(proID)
local newShieldDamage = originalShieldDamages[weaponDefID] or fallbackShieldDamage
shieldData.shieldDamage = shieldData.shieldDamage + newShieldDamage

if forceDeleteWeapons[weaponDefID] then
-- Flames and penetrating projectiles aren't destroyed when they hit shields, so need to delete manually
spDeleteProjectile(proID)
Expand All @@ -452,45 +451,41 @@ function gadget:ShieldPreDamaged(proID, proOwnerID, shieldWeaponNum, shieldUnitI
removeCoveredUnits(shieldUnitID)
end
end

do
---Shield controller API for other gadgets to generate and process their own shield damage events.
local function addShieldDamage(shieldUnitID, shieldWeaponNumber, damage, weaponDefID, projectileID,
beamEmitterWeaponNum, beamEmitterUnitID)
local projectileDestroyed, damageMitigated = false, 0
if not beamEmitterUnitID and beamEmitterWeapons[weaponDefID] then
beamEmitterUnitID, beamEmitterWeaponNum = unpack(beamEmitterWeapons[weaponDefID])
end
local shieldData = shieldUnitsData[shieldUnitID]
if shieldData and shieldData.shieldEnabled then
local shieldDamage = shieldData.shieldDamage
local result = gadget:ShieldPreDamaged(projectileID, nil, shieldWeaponNumber, shieldUnitID, nil,
beamEmitterWeaponNum, beamEmitterUnitID)
if result == nil then
projectileDestroyed = true
if damage then
shieldData.shieldDamage = shieldDamage + damage
damageMitigated = damage
else
damageMitigated = shieldData.shieldDamage - shieldDamage
end
end
end
return projectileDestroyed, damageMitigated
end

function gadget:Initialize()
GG.AddShieldDamage = addShieldDamage

for _, unitID in ipairs(Spring.GetAllUnits()) do
local unitDefID = Spring.GetUnitDefID(unitID)
local unitTeam = Spring.GetUnitTeam(unitID)
gadget:UnitFinished(unitID, unitDefID, unitTeam)
end
---Shield controller API for other gadgets to generate and process their own shield damage events.
local function addShieldDamage(shieldUnitID, damage, weaponDefID, projectileID, beamEmitterWeaponNum, beamEmitterUnitID)
local projectileDestroyed, damageMitigated = false, 0
if not beamEmitterUnitID and beamEmitterWeapons[weaponDefID] then
beamEmitterUnitID, beamEmitterWeaponNum = unpack(beamEmitterWeapons[weaponDefID])
end
local shieldData = shieldUnitsData[shieldUnitID]
if shieldData and shieldData.shieldEnabled then
local shieldDamage = shieldData.shieldDamage
local result = gadget:ShieldPreDamaged(projectileID, nil, shieldData.shieldWeaponNumber, shieldUnitID, nil,
beamEmitterWeaponNum, beamEmitterUnitID)
if result == nil then
projectileDestroyed = true
if damage then
shieldData.shieldDamage = shieldDamage + damage
damageMitigated = damage
else
damageMitigated = shieldData.shieldDamage - shieldDamage
end
end
end
return projectileDestroyed, damageMitigated
end

function gadget:ShutDown()
GG.AddShieldDamage = nil
function gadget:Initialize()
GG.AddShieldDamage = addShieldDamage

for _, unitID in ipairs(Spring.GetAllUnits()) do
local unitDefID = Spring.GetUnitDefID(unitID)
local unitTeam = Spring.GetUnitTeam(unitID)
gadget:UnitFinished(unitID, unitDefID, unitTeam)
end
end

function gadget:ShutDown()
GG.AddShieldDamage = nil
end

0 comments on commit 09bdb66

Please sign in to comment.