Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions changelog/snippets/features.6937.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
- (#6937) Implement the ability to adjust shield assist costs using the following blueprint values:

- `RegenPerBuildRate`: How much HP/s is restored per unit of buildpower assisting the shield. Overrides `RegenAssistMult`.
- `AssistCostEnergyPerBuildRate` and `AssistCostMassPerBuildRate`: The resource cost per second per unit of buildpower assisting the shield.

Both values must be present to have an effect, otherwise it defaults to the repair cost of the shield structure (75% of its build cost).

If both the HP and the shield of a unit are damaged, the assist cost and effects are split equally between the HP repair cost and shield assist costs.

These values should be assigned in either the `Defense.Shield` or `Enhancements.<EnhancementName>` tables of the unit blueprint.

Please keep in mind that shield assist only works for units with the `SHIELD` category, and only if they're not upgrading themselves/building something.
13 changes: 13 additions & 0 deletions engine/Core/Blueprints/UnitBlueprint.lua
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,19 @@
--- If this shield is one that unequivocally protects all attached units.
--- Should not be defined with `AntiArtilleryShield`, `PersonalBubble`, or `PersonalShield`.
---@field TransportShield? boolean
--- How much HP/s is restored per unit of buildpower assisting the shield.
--- Overrides `RegenAssistMult` during blueprint loading.
---@field RegenPerBuildRate number
--- The energy cost per second per unit of buildpower assisting the shield.
--- Must be present alongside `AssistCostMassPerBuildRate` to have an effect.
--- If both shield and unit HP are damaged, the costs/effects are split equally between HP repair cost
--- and shield assist cost.
---@field AssistCostEnergyPerBuildRate number
--- The mass cost per second per unit of buildpower assisting the shield.
--- Must be present alongside `AssistCostEnergyPerBuildRate` to have an effect.
--- If both shield and unit HP are damaged, the costs/effects are split equally between HP repair cost
--- and shield assist cost.
---@field AssistCostMassPerBuildRate number

---@class UnitBlueprintBlinkingLightsData
---@field BLBone Bone
Expand Down
10 changes: 9 additions & 1 deletion engine/Sim/Unit.lua
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,11 @@ end
function Unit:GetFireState()
end

---@return Unit
--- Gets the unit's focus unit, which is usually the unit it is building/repairing/reclaiming/capturing.
--- The focus may be set to an Entity (usually a Shield) or a Projectile, in which case this
--- will return nil.
---@see SetFocusEntity
---@return Unit?
function Unit:GetFocusUnit()
end

Expand Down Expand Up @@ -557,6 +561,10 @@ end
function Unit:SetFireState(fireState)
end

--- Sets the focus entity of the unit.
--- Usually set by the engine when an engineer starts building/repairing/reclaiming/capturing units.
--- Shield units set their shield entity as the focus to make it repairable.
---@see GetFocusUnit
---@param focus FocusObject
function Unit:SetFocusEntity(focus)
end
Expand Down
20 changes: 20 additions & 0 deletions lua/shield.lua
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ end
---@field SkipAttachmentCheck boolean
---@field AbsorptionTypeDamageTypeToMulti table<DamageType, number>
---@field DisallowCollisions boolean
---@field AssistCostEnergyPerBuildRate? number
---@field AssistCostMassPerBuildRate? number
Shield = ClassShield(moho.shield_methods, Entity) {

RemainEnabledWhenAttached = false,
Expand Down Expand Up @@ -195,6 +197,8 @@ Shield = ClassShield(moho.shield_methods, Entity) {
self.PassOverkillDamage = spec.PassOverkillDamage
self.ImpactMeshBp = spec.ImpactMesh
self.SkipAttachmentCheck = spec.SkipAttachmentCheck
self.AssistCostEnergyPerBuildRate = spec.AssistCostEnergyPerBuildRate
self.AssistCostMassPerBuildRate = spec.AssistCostMassPerBuildRate
self.DisallowCollisions = false

if spec.ImpactEffects ~= '' then
Expand Down Expand Up @@ -315,11 +319,27 @@ Shield = ClassShield(moho.shield_methods, Entity) {
-- adjust shield bar one last time
self:UpdateShieldRatio(health / maxHealth)

-- Manage shield assisters: shield is full HP and cannot be assisted anymore
if health == maxHealth
and self.AssistCostEnergyPerBuildRate and self.AssistCostMassPerBuildRate
then
for _, unit in self.Owner.Repairers do
unit:UpdateConsumptionValues()
end
end

-- suspend ourselves and wait
self.RegenThreadSuspended = true
SuspendCurrentThread()
self.RegenThreadSuspended = false
fromSuspension = true

-- Manage shield assisters: shield was damaged from full HP and can now be assisted
if self.AssistCostEnergyPerBuildRate and self.AssistCostMassPerBuildRate then
for _, unit in self.Owner.Repairers do
unit:UpdateConsumptionValues()
end
end
end

-- if we didn't suspend then check regeneration conditions
Expand Down
79 changes: 75 additions & 4 deletions lua/sim/Unit.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1179,6 +1179,18 @@ Unit = ClassUnit(moho.unit_methods, IntelComponent, VeterancyComponent, DebugUni
end
end,

---@param self Unit
UpdateShieldAssistersConsumption = function(self)
if self.Blueprint.CategoriesHash["SHIELD"] then
local myShield = self.MyShield
if myShield.AssistCostEnergyPerBuildRate and myShield.AssistCostMassPerBuildRate then
for _, unit in self.Repairers do
unit:UpdateConsumptionValues()
end
end
end
end,

-- Called when we start building a unit, turn on/off, get/lose bonuses, or on
-- any other change that might affect our build rate or resource use.
---@param self Unit
Expand Down Expand Up @@ -1221,12 +1233,62 @@ Unit = ClassUnit(moho.unit_methods, IntelComponent, VeterancyComponent, DebugUni
time, energy, mass = focus:GetBuildCosts(focus.SiloProjectile)
energy = (energy / siloBuildRate) * (self:GetBuildRate() or 0)
mass = (mass / siloBuildRate) * (self:GetBuildRate() or 0)
else
time, energy, mass = self:GetBuildCosts(focus:GetBlueprint())
if self:IsUnitState('Repairing') and focus.isFinishedUnit then -- also applies to shield assisting
elseif self:IsUnitState('Repairing') and focus.isFinishedUnit then
-- repairing a unit or assisting a shield
local function SetDefaultRepairCosts()
time, energy, mass = self:GetBuildCosts(focus:GetBlueprint())
energy = energy * repairRatio
mass = mass * repairRatio
end

if not focus.Blueprint.CategoriesHash["SHIELD"] then
-- units without SHIELD category cannot be shield assisted
SetDefaultRepairCosts()
else
local focusShield = focus.MyShield
local shieldAssistEnergy = focusShield.AssistCostEnergyPerBuildRate
local shieldAssistMass = focusShield.AssistCostMassPerBuildRate

if not focusShield
-- units default to repair cost for shield assist costs
or not shieldAssistEnergy
or not shieldAssistMass
-- units not focused on a shield that is up cannot be shield assisted
or not focusShield:IsUp()
or not focus:GetFocusUnit() == nil
then
SetDefaultRepairCosts()
else
-- Determine what we are repairing, since they have different costs
local repairingFocusUnit = focus:GetMaxHealth() > focus:GetHealth()
local repairingFocusShield = focusShield:GetMaxHealth() > focusShield:GetHealth()

if repairingFocusUnit then
SetDefaultRepairCosts()
-- Engine splits repair effect 50/50 so reduce costs in that case
if repairingFocusShield then
energy = energy * 0.5
mass = mass * 0.5
end
end

if repairingFocusShield then
local buildRate = self:GetBuildRate()
-- Engine splits repair effect 50/50 so reduce costs in that case
if repairingFocusUnit then
shieldAssistEnergy = shieldAssistEnergy * 0.5
shieldAssistMass = shieldAssistMass * 0.5
end
energy = energy + shieldAssistEnergy * buildRate * time
mass = mass + shieldAssistMass * buildRate * time
end
end
end
else
-- building a unit
time, energy, mass = self:GetBuildCosts(focus:GetBlueprint())
energy = energy * repairRatio
mass = mass * repairRatio
end
end

Expand Down Expand Up @@ -1432,6 +1494,11 @@ Unit = ClassUnit(moho.unit_methods, IntelComponent, VeterancyComponent, DebugUni

-- inform the brain of the event
self.Brain:OnUnitHealthChanged(self, new, old)

-- Manage shield assisters: unit is damaged/no longer damaged so assist consumption changes
if new == 1 or old == 1 then
self:UpdateShieldAssistersConsumption()
end
end,

---@param self Unit
Expand Down Expand Up @@ -4546,8 +4613,9 @@ Unit = ClassUnit(moho.unit_methods, IntelComponent, VeterancyComponent, DebugUni
end
end,

--- Called by the engine to determine whether or not the unit's shield can be assisted
---@param self Unit
---@return boolean
---@return boolean?
ShieldIsOn = function(self)
if self.MyShield then
return self.MyShield:IsOn()
Expand Down Expand Up @@ -5284,6 +5352,9 @@ Unit = ClassUnit(moho.unit_methods, IntelComponent, VeterancyComponent, DebugUni
OnShieldDisabled = function(self)
-- for AI events
self.Brain:OnUnitShieldDisabled(self)

-- Manage shield assisters: shield is disabled and cannot be assisted anymore
self:UpdateShieldAssistersConsumption()
end,

-- Called by the brain when the unit registered itself
Expand Down
40 changes: 40 additions & 0 deletions lua/system/blueprints-units.lua
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,30 @@ local function DetermineWeaponCategory(weapon)
return nil
end

--- Adjusts `RegenAssistMult` of a shield blueprint according to its `RegenPerBuildRate`
---@param unit UnitBlueprint
---@param shieldBp UnitBlueprintDefenseShield
local function AdjustShieldAssistCost(unit, shieldBp)
local regenPerBuildRate = shieldBp.RegenPerBuildRate
if regenPerBuildRate then
local regen = shieldBp.ShieldRegenRate
-- RegenAssistMult is used by the engine to determine how much buildpower is needed to
-- provide 1x the shield's regen rate as HP restored.
-- Exception: RegenRate is read from the Shield's lua table during run time, so
-- the actual HP restored/second may vary from blueprint spec.

-- HP Restored/second = RegenRate * Buildpower / RegenAssistMult
-- We want to be able to assign it simply using HP Restored/s per buildpower.
-- HPR = RR * BP / RAM
-- HPR * RAM = RR * BP
-- RAM = RR * BP / HPR
-- RAM = RR * 1 / (HPR/BP)
-- RAM = RR / (HPR/BP)

shieldBp.RegenAssistMult = regen / shieldBp.RegenPerBuildRate
end
end

--- Post process a unit
---@param unit UnitBlueprint
local function PostProcessUnit(unit)
Expand Down Expand Up @@ -614,6 +638,22 @@ local function PostProcessUnit(unit)
unit.Display.AnimationDeath = nil
end
end

--#region Adjust shield assist effectiveness
-- Engine only implements assist for SHIELD category units
if unit.CategoriesHash["SHIELD"] then
if unit.Defense.Shield then
AdjustShieldAssistCost(unit, unit.Defense.Shield)
end
if unit.Enhancements then
for _, enh in unit.Enhancements do
if enh.ShieldSize then
AdjustShieldAssistCost(unit, unit.Defense.Shield)
end
end
end
end
--#endregion
end

--- Feature: re-apply the ability to land on water
Expand Down
Loading