Skip to content

Commit

Permalink
Simplify and robustify transport cargo killing and veterancy (FAForev…
Browse files Browse the repository at this point in the history
…er#6091)

Co-authored-by: lL1l1 <[email protected]>
  • Loading branch information
clyfordv and lL1l1 authored Apr 22, 2024
1 parent d3ef3c0 commit 5f7b2a9
Show file tree
Hide file tree
Showing 10 changed files with 194 additions and 130 deletions.
2 changes: 2 additions & 0 deletions changelog/3810.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@

- (#6086) Ensure that the Cooper's hitbox aligns with its model

- (#6091) Count cargo for veterancy when a transport or carrier is killed

## Contributors

With thanks to the following people who contributed through coding:
Expand Down
7 changes: 4 additions & 3 deletions engine/Sim/Entity.lua
Original file line number Diff line number Diff line change
Expand Up @@ -281,9 +281,10 @@ end
function Entity:IsValidBone(bone, allowNil)
end

---@param instigator? Unit
---@param damageType? DamageType
---@param excessDamageRatio? number
---@overload fun():
---@param instigator Unit
---@param damageType DamageType
---@param excessDamageRatio number
function Entity:Kill(instigator, damageType, excessDamageRatio)
end

Expand Down
1 change: 1 addition & 0 deletions lua/armordefinition.lua
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
---| "TreeFire"
---| "TreeForce"
---| "WallOverspill"
---| "TransportDamage" # Skips visual effects in OnKilled

---@alias ArmorType
---| "ASF"
Expand Down
5 changes: 3 additions & 2 deletions lua/defaultcomponents.lua
Original file line number Diff line number Diff line change
Expand Up @@ -641,8 +641,9 @@ VeterancyComponent = ClassSimple {

--- Disperses the veterancy, expects to be only called once
---@param self VeterancyComponent | Unit
VeterancyDispersal = function(self)
local vetWorth = self:GetFractionComplete() * self:GetTotalMassCost()
---@param experience? number -- override for amount of experience to be distributed
VeterancyDispersal = function(self, experience)
local vetWorth = experience or (self:GetFractionComplete() * self:GetTotalMassCost())
local vetDamage = self.VetDamage
local vetInstigators = self.VetInstigators
local vetDamageTaken = self.VetDamageTaken
Expand Down
69 changes: 39 additions & 30 deletions lua/sim/Unit.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1492,45 +1492,49 @@ Unit = ClassUnit(moho.unit_methods, IntelComponent, VeterancyComponent) {
local bp = self.Blueprint
local army = self.Army

-- Units killed while being invisible because they're teleporting should show when they're killed
if self.TeleportFx_IsInvisible then
self:ShowBone(0, true)
self:ShowEnhancementBones()
end
-- Skip all audio/visual/death threads/shields/etc. if we're in internal storage
-- (presumably these should already have been removed when the unit entered storage)
if type ~= "TransportDamage" then
-- Units killed while being invisible because they're teleporting should show when they're killed
if self.TeleportFx_IsInvisible then
self:ShowBone(0, true)
self:ShowEnhancementBones()
end

if layer == 'Water' and bp.Physics.MotionType == 'RULEUMT_Hover' then
self:PlayUnitSound('HoverKilledOnWater')
elseif layer == 'Land' and bp.Physics.MotionType == 'RULEUMT_AmphibiousFloating' then
-- Handle ships that can walk on land
self:PlayUnitSound('AmphibiousFloatingKilledOnLand')
else
self:PlayUnitSound('Killed')
end

if layer == 'Water' and bp.Physics.MotionType == 'RULEUMT_Hover' then
self:PlayUnitSound('HoverKilledOnWater')
elseif layer == 'Land' and bp.Physics.MotionType == 'RULEUMT_AmphibiousFloating' then
-- Handle ships that can walk on land
self:PlayUnitSound('AmphibiousFloatingKilledOnLand')
else
self:PlayUnitSound('Killed')
end
-- apply death animation on half built units (do not apply for ML and mega)
local FractionThreshold = bp.General.FractionThreshold or 0.5
if self.PlayDeathAnimation and self:GetFractionComplete() > FractionThreshold then
self:ForkThread(self.PlayAnimationThread, 'AnimationDeath')
self.DisallowCollisions = true
end

-- apply death animation on half built units (do not apply for ML and mega)
local FractionThreshold = bp.General.FractionThreshold or 0.5
if self.PlayDeathAnimation and self:GetFractionComplete() > FractionThreshold then
self:ForkThread(self.PlayAnimationThread, 'AnimationDeath')
self.DisallowCollisions = true
end
self:DoUnitCallbacks('OnKilled')
if self.UnitBeingTeleported and not self.UnitBeingTeleported.Dead then
self.UnitBeingTeleported:Destroy()
self.UnitBeingTeleported = nil
end

self:DoUnitCallbacks('OnKilled')
if self.UnitBeingTeleported and not self.UnitBeingTeleported.Dead then
self.UnitBeingTeleported:Destroy()
self.UnitBeingTeleported = nil
end
if self.DeathWeaponEnabled ~= false then
self:DoDeathWeapon()
end

if self.DeathWeaponEnabled ~= false then
self:DoDeathWeapon()
self:DisableShield()
self:DisableUnitIntel('Killed')
self:ForkThread(self.DeathThread, overkillRatio , instigator)
end

-- veterancy computations should happen after triggering death weapons
VeterancyComponent.VeterancyDispersal(self)

self:DisableShield()
self:DisableUnitIntel('Killed')
self:ForkThread(self.DeathThread, overkillRatio , instigator)

-- awareness for traitor game mode and game statistics
ArmyBrains[army].LastUnitKilledBy = (instigator or self).Army
ArmyBrains[army]:AddUnitStat(self.UnitId, "lost", 1)
Expand All @@ -1541,6 +1545,11 @@ Unit = ClassUnit(moho.unit_methods, IntelComponent, VeterancyComponent) {
end

self.Brain:OnUnitKilled(self, instigator, type, overkillRatio)

-- If we're in internal storage, destroy the unit since DeathThread isn't played to destroy it
if type == "TransportDamage" then
self:Destroy()
end
end,

---@param self Unit
Expand Down
59 changes: 13 additions & 46 deletions lua/sim/units/AirTransportUnit.lua
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ local VertUnloadFactor = 1.5

---@class AirTransport: AirUnit, BaseTransport
---@field slots table<Bone, Unit>
---@field GroundImpacted boolean
AirTransport = ClassUnit(AirUnit, BaseTransport) {
---@param self AirTransport
OnCreate = function(self)
Expand Down Expand Up @@ -114,25 +115,28 @@ AirTransport = ClassUnit(AirUnit, BaseTransport) {
end,

---@param self AirTransport
---@param instigator? Unit
---@param instigator Unit
---@param damageType? DamageType
---@param excessDamageRatio? number
Kill = function(self, instigator, damageType, excessDamageRatio)
-- needs to be defined
-- handle the cargo killing
-- skip for transports inside other transports, as our KillCargo will have
-- already been recursively called from the parent transports KillCargo call
if damageType ~= "TransportDamage" then
self:KillCargo(instigator)
end
-- these need to be defined for certain behaviors (like ctrl-k) to function
damageType = damageType or "Normal"
excessDamageRatio = excessDamageRatio or 0

self:FlagCargo(not instigator or not IsUnit(instigator))
excessDamageRatio = excessDamageRatio or 0
AirUnitKill(self, instigator, damageType, excessDamageRatio)
end,

-- Override OnImpact to kill all cargo
-- Override OnImpact to dispense with our cargo
---@param self AirTransport
---@param with AirTransport
---@param with ImpactType
OnImpact = function(self, with)
if self.GroundImpacted then return end

self:KillCrashedCargo()
self:ImpactCargo()
AirUnitOnImpact(self, with)
end,

Expand All @@ -144,41 +148,4 @@ AirTransport = ClassUnit(AirUnit, BaseTransport) {
v:OnStorageChange(loading)
end
end,

-- Flags cargo that it's been killed while in a transport
---@param self AirTransport
---@param suicide boolean
FlagCargo = function(self, suicide)
if self.Dead then return end -- Bail out early from overkill damage when already dead to avoid crashing

if not suicide then -- If the transport is self destructed, let its contents be self destructed separately
self:SaveCargoMass()
end
self.cargo = {}
local cargo = self:GetCargo()
for _, unit in cargo or {} do
if EntityCategoryContains(categories.TRANSPORTATION, unit) then -- Kill the contents of a transport in a transport, however that happened
local unitCargo = unit:GetCargo()
for k, subUnit in unitCargo do
subUnit:Kill()
end
end
if not EntityCategoryContains(categories.COMMAND, unit) then
unit.killedInTransport = true
table.insert(self.cargo, unit)
end
end
end,

---@param self BaseTransport
KillCrashedCargo = function(self)
if self:BeenDestroyed() then return end

for _, unit in self.cargo or {} do
if not unit:BeenDestroyed() then
unit.DeathWeaponEnabled = false -- Units at this point have no weapons for some reason. Trying to fire one crashes the game.
unit:OnKilled(nil, '', 0)
end
end
end,
}
62 changes: 35 additions & 27 deletions lua/sim/units/AirUnit.lua
Original file line number Diff line number Diff line change
Expand Up @@ -182,36 +182,39 @@ AirUnit = ClassUnit(MobileUnit) {
(self.Layer == 'Air' or EntityCategoryContains(categories.TRANSPORTATION, self))
then
self.Dead = true
self:CreateUnitAirDestructionEffects(1.0)
self:DestroyTopSpeedEffects()
self:DestroyBeamExhaust()
self.OverKillRatio = overkillRatio
self:PlayUnitSound('Killed')
self:DoUnitCallbacks('OnKilled')
self:DisableShield()

-- Store our death weapon's damage on the unit so it can be edited remotely by the shield bouncer projectile
local bp = self.Blueprint
local i = 1
for i, numweapons in bp.Weapon do
if bp.Weapon[i].Label == 'DeathImpact' then
self.deathWep = bp.Weapon[i]
break
-- We want to skip all the visual/audio/shield bounce/death weapon stuff if we're in internal storage
if type ~= "TransportDamage" then
self:CreateUnitAirDestructionEffects(1.0)
self:DestroyTopSpeedEffects()
self:DestroyBeamExhaust()
self.OverKillRatio = overkillRatio
self:PlayUnitSound('Killed')
self:DoUnitCallbacks('OnKilled')
self:DisableShield()

-- Store our death weapon's damage on the unit so it can be edited remotely by the shield bouncer projectile
local bp = self.Blueprint
local i = 1
for i, numweapons in bp.Weapon do
if bp.Weapon[i].Label == 'DeathImpact' then
self.deathWep = bp.Weapon[i]
break
end
end
end

if not self.deathWep or self.deathWep == {} then
WARN(string.format('(%s) has no death weapon or the death weapon has an incorrect label!',
tostring(bp.BlueprintId)))
else
self.DeathCrashDamage = self.deathWep.Damage
end
if not self.deathWep or self.deathWep == {} then
WARN(string.format('(%s) has no death weapon or the death weapon has an incorrect label!',
tostring(bp.BlueprintId)))
else
self.DeathCrashDamage = self.deathWep.Damage
end

-- Create a projectile we'll use to interact with Shields
local proj = self:CreateProjectileAtBone('/projectiles/ShieldCollider/ShieldCollider_proj.bp', 0)
self.colliderProj = proj
proj:Start(self, 0)
self.Trash:Add(proj)
-- Create a projectile we'll use to interact with Shields
local proj = self:CreateProjectileAtBone('/projectiles/ShieldCollider/ShieldCollider_proj.bp', 0)
self.colliderProj = proj
proj:Start(self, 0)
self.Trash:Add(proj)
end

self:VeterancyDispersal()

Expand All @@ -226,6 +229,11 @@ AirUnit = ClassUnit(MobileUnit) {
end

self.Brain:OnUnitKilled(self, instigator, type, overkillRatio)

-- If we're in internal storage, we're done, destroy the unit to avoid OnImpact errors
if type == "TransportDamage" then
self:Destroy()
end
else
MobileUnitOnKilled(self, instigator, type, overkillRatio)
end
Expand Down
21 changes: 14 additions & 7 deletions lua/sim/units/AircraftCarrierUnit.lua
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
--**********************************************************************************

local SeaUnit = import("/lua/sim/units/seaunit.lua").SeaUnit
local SeaUnitOnKilled = SeaUnit.OnKilled
local SeaUnitKill = SeaUnit.Kill
local SeaUnitOnTransportAttach = SeaUnit.OnTransportAttach
local SeaUnitOnTransportDetach = SeaUnit.OnTransportDetach
local SeaUnitOnAttachedKilled = SeaUnit.OnAttachedKilled
Expand Down Expand Up @@ -82,11 +82,18 @@ AircraftCarrier = ClassUnit(SeaUnit, BaseTransport) {

---@param self AircraftCarrier
---@param instigator Unit
---@param type string
---@param overkillRatio number
OnKilled = function(self, instigator, type, overkillRatio)
self:SaveCargoMass()
SeaUnitOnKilled(self, instigator, type, overkillRatio)
self:DetachCargo()
---@param damageType? string
---@param excessDamageRatio? number
Kill = function(self, instigator, damageType, excessDamageRatio)
-- handle the cargo killing
-- skip for transports inside other transports, as our KillCargo will have
-- already been recursively called from the parent transports KillCargo call
if damageType ~= "TransportDamage" then
self:KillCargo(instigator)
end
-- certain behaviors (like ctrl-k) will call this with nil parameters, but they must be defined for the engine's Kill function to work
damageType = damageType or ""
excessDamageRatio = excessDamageRatio or 0
SeaUnitKill(self, instigator, damageType, excessDamageRatio)
end,
}
3 changes: 2 additions & 1 deletion lua/sim/units/MobileUnit.lua
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ MobileUnit = ClassUnit(Unit, TreadComponent) {
---@param type string
---@param overkillRatio number
OnKilled = function(self, instigator, type, overkillRatio)
-- OnKilled will be called a second time by the transport's OnImpact
-- Skips a single OnKilled call
-- currently used by transports with external storage, so that death effects can be applied later from OnImpact
if self.killedInTransport then
self.killedInTransport = false
else
Expand Down
Loading

0 comments on commit 5f7b2a9

Please sign in to comment.