From 337185dbb3b707c768ad4b440d36ceaf3767f145 Mon Sep 17 00:00:00 2001 From: Alexander <84857900+4z0t@users.noreply.github.com> Date: Thu, 31 Oct 2024 13:11:11 +0300 Subject: [PATCH 01/33] Document CMauiBitmap:SetColorMask (#6500) --- engine/User/CMauiBitmap.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/engine/User/CMauiBitmap.lua b/engine/User/CMauiBitmap.lua index 278b891143..8a5f7f851b 100644 --- a/engine/User/CMauiBitmap.lua +++ b/engine/User/CMauiBitmap.lua @@ -33,6 +33,11 @@ end function CMauiBitmap:SetForwardPattern() end +---Sets color mask applied to bitmap during rendering (white images will get this color for example) +---@param color Color +function CMauiBitmap:SetColorMask(color) +end + --- ---@param frame number function CMauiBitmap:SetFrame(frame) From 641d59814ba2d53d536edbc03157af0b0592ce78 Mon Sep 17 00:00:00 2001 From: Josh Date: Thu, 31 Oct 2024 03:19:41 -0700 Subject: [PATCH 02/33] Improve maintainability of UEF ACU enhancements (#6498) --- changelog/snippets/other.6498.md | 1 + units/UEL0001/UEL0001_script.lua | 597 +++++++++++++++++++------------ 2 files changed, 376 insertions(+), 222 deletions(-) create mode 100644 changelog/snippets/other.6498.md diff --git a/changelog/snippets/other.6498.md b/changelog/snippets/other.6498.md new file mode 100644 index 0000000000..6387b72138 --- /dev/null +++ b/changelog/snippets/other.6498.md @@ -0,0 +1 @@ +- (#6498) Refactor the Enhancements section in the Unit script file to replace the long if/else chain with a more modular design. Each enhancement should have its own dedicated function, enabling cleaner code organization and easier maintenance. The CreateEnhancement function will then call the appropriate enhancement function based on specific enhancement criteria. diff --git a/units/UEL0001/UEL0001_script.lua b/units/UEL0001/UEL0001_script.lua index 80dfcab0bc..3be7b2711a 100644 --- a/units/UEL0001/UEL0001_script.lua +++ b/units/UEL0001/UEL0001_script.lua @@ -302,239 +302,392 @@ UEL0001 = ClassUnit(ACUUnit) { attachee:SetDoNotTarget(false) end, + --------------------------------------------------------------------------- + --#region Enhancements + + -- Drone Upgrades + ---@param self UEL0001 - ---@param enh string - CreateEnhancement = function(self, enh) - ACUUnit.CreateEnhancement(self, enh) + ---@param bp Blueprint unused + ProcessEnhancementLeftPod = function(self, bp) + local location = self:GetPosition('AttachSpecial02') + local pod = CreateUnitHPR('UEA0001', self.Army, location[1], location[2], location[3], 0, 0, 0) + pod:SetParent(self, 'LeftPod') + pod:SetCreator(self) + self.Trash:Add(pod) + self.HasLeftPod = true + self.LeftPod = pod + end, - local bp = self:GetBlueprint().Enhancements[enh] - if not bp then return end - if enh == 'LeftPod' then - local location = self:GetPosition('AttachSpecial02') - local pod = CreateUnitHPR('UEA0001', self.Army, location[1], location[2], location[3], 0, 0, 0) - pod:SetParent(self, 'LeftPod') - pod:SetCreator(self) - self.Trash:Add(pod) - self.HasLeftPod = true - self.LeftPod = pod - elseif enh == 'RightPod' then - local location = self:GetPosition('AttachSpecial01') - local pod = CreateUnitHPR('UEA0001', self.Army, location[1], location[2], location[3], 0, 0, 0) - pod:SetParent(self, 'RightPod') - pod:SetCreator(self) - self.Trash:Add(pod) - self.HasRightPod = true - self.RightPod = pod - elseif enh == 'LeftPodRemove' or enh == 'RightPodRemove' then - if self.HasLeftPod == true then - self.HasLeftPod = false - if self.LeftPod and not self.LeftPod.Dead then - self.LeftPod:Kill() - self.LeftPod = nil - end - if self.RebuildingPod ~= nil then - RemoveEconomyEvent(self, self.RebuildingPod) - self.RebuildingPod = nil - end - end - if self.HasRightPod == true then - self.HasRightPod = false - if self.RightPod and not self.RightPod.Dead then - self.RightPod:Kill() - self.RightPod = nil - end - if self.RebuildingPod2 ~= nil then - RemoveEconomyEvent(self, self.RebuildingPod2) - self.RebuildingPod2 = nil - end + ---@param self UEL0001 + ---@param bp Blueprint unused + ProcessEnhancementLeftPodRemove = function(self, bp) + if self.HasLeftPod == true then + self.HasLeftPod = false + if self.LeftPod and not self.LeftPod.Dead then + self.LeftPod:Kill() + self.LeftPod = nil end - KillThread(self.RebuildThread) - KillThread(self.RebuildThread2) - elseif enh == 'Teleporter' then - self:AddCommandCap('RULEUCC_Teleport') - elseif enh == 'TeleporterRemove' then - self:RemoveCommandCap('RULEUCC_Teleport') - elseif enh == 'Shield' then - self:AddToggleCap('RULEUTC_ShieldToggle') - self:CreateShield(bp) - self:SetEnergyMaintenanceConsumptionOverride(bp.MaintenanceConsumptionPerSecondEnergy or 0) - self:SetMaintenanceConsumptionActive() - elseif enh == 'ShieldRemove' then - self:DestroyShield() - self:SetMaintenanceConsumptionInactive() - RemoveUnitEnhancement(self, 'ShieldRemove') - self:RemoveToggleCap('RULEUTC_ShieldToggle') - elseif enh == 'ShieldGeneratorField' then - self:AddToggleCap('RULEUTC_ShieldToggle') - self:DestroyShield() - self:ForkThread( - function() - WaitTicks(1) - self:CreateShield(bp) - self:SetEnergyMaintenanceConsumptionOverride(bp.MaintenanceConsumptionPerSecondEnergy or 0) - self:SetMaintenanceConsumptionActive() - end - ) - elseif enh == 'ShieldGeneratorFieldRemove' then - self:DestroyShield() - self:SetMaintenanceConsumptionInactive() - self:RemoveToggleCap('RULEUTC_ShieldToggle') - elseif enh == 'AdvancedEngineering' then - local cat = ParseEntityCategory(bp.BuildableCategoryAdds) - self:RemoveBuildRestriction(cat) - if not Buffs['UEFACUT2BuildRate'] then - BuffBlueprint { - Name = 'UEFACUT2BuildRate', - DisplayName = 'UEFACUT2BuildRate', - BuffType = 'ACUBUILDRATE', - Stacks = 'REPLACE', - Duration = -1, - Affects = { - BuildRate = { - Add = bp.NewBuildRate - self:GetBlueprint().Economy.BuildRate, - Mult = 1, - }, - MaxHealth = { - Add = bp.NewHealth, - Mult = 1.0, - }, - Regen = { - Add = bp.NewRegenRate, - Mult = 1.0, - }, - }, - } + if self.RebuildingPod ~= nil then + RemoveEconomyEvent(self, self.RebuildingPod) + self.RebuildingPod = nil end - Buff.ApplyBuff(self, 'UEFACUT2BuildRate') - elseif enh == 'AdvancedEngineeringRemove' then - local bp = self:GetBlueprint().Economy.BuildRate - if not bp then return end - self:RestoreBuildRestrictions() - self:AddBuildRestriction(categories.UEF * - (categories.BUILTBYTIER2COMMANDER + categories.BUILTBYTIER3COMMANDER)) - self:AddBuildRestriction(categories.UEF * - (categories.BUILTBYTIER2COMMANDER + categories.BUILTBYTIER3COMMANDER)) - if Buff.HasBuff(self, 'UEFACUT2BuildRate') then - Buff.RemoveBuff(self, 'UEFACUT2BuildRate') + end + KillThread(self.RebuildThread) + end, + + ---@param self UEL0001 + ---@param bp Blueprint unused + ProcessEnhancementRightPod = function(self, bp) + local location = self:GetPosition('AttachSpecial01') + local pod = CreateUnitHPR('UEA0001', self.Army, location[1], location[2], location[3], 0, 0, 0) + pod:SetParent(self, 'RightPod') + pod:SetCreator(self) + self.Trash:Add(pod) + self.HasRightPod = true + self.RightPod = pod + end, + + ---@param self UEL0001 + ---@param bp Blueprint unused + ProcessEnhancementRightPodRemove = function(self, bp) + if self.HasLeftPod == true then + self.HasLeftPod = false + if self.LeftPod and not self.LeftPod.Dead then + self.LeftPod:Kill() + self.LeftPod = nil end - elseif enh == 'T3Engineering' then - local cat = ParseEntityCategory(bp.BuildableCategoryAdds) - self:RemoveBuildRestriction(cat) - if not Buffs['UEFACUT3BuildRate'] then - BuffBlueprint { - Name = 'UEFACUT3BuildRate', - DisplayName = 'UEFCUT3BuildRate', - BuffType = 'ACUBUILDRATE', - Stacks = 'REPLACE', - Duration = -1, - Affects = { - BuildRate = { - Add = bp.NewBuildRate - self:GetBlueprint().Economy.BuildRate, - Mult = 1, - }, - MaxHealth = { - Add = bp.NewHealth, - Mult = 1.0, - }, - Regen = { - Add = bp.NewRegenRate, - Mult = 1.0, - }, - }, - } + if self.RebuildingPod ~= nil then + RemoveEconomyEvent(self, self.RebuildingPod) + self.RebuildingPod = nil end - Buff.ApplyBuff(self, 'UEFACUT3BuildRate') - elseif enh == 'T3EngineeringRemove' then - local bp = self:GetBlueprint().Economy.BuildRate - if not bp then return end - self:RestoreBuildRestrictions() - if Buff.HasBuff(self, 'UEFACUT3BuildRate') then - Buff.RemoveBuff(self, 'UEFACUT3BuildRate') + end + if self.HasRightPod == true then + self.HasRightPod = false + if self.RightPod and not self.RightPod.Dead then + self.RightPod:Kill() + self.RightPod = nil end - self:AddBuildRestriction(categories.UEF * - (categories.BUILTBYTIER2COMMANDER + categories.BUILTBYTIER3COMMANDER)) - elseif enh == 'DamageStabilization' then - if not Buffs['UEFACUDamageStabilization'] then - BuffBlueprint { - Name = 'UEFACUDamageStabilization', - DisplayName = 'UEFACUDamageStabilization', - BuffType = 'DamageStabilization', - Stacks = 'REPLACE', - Duration = -1, - Affects = { - MaxHealth = { - Add = bp.NewHealth, - Mult = 1.0, - }, - Regen = { - Add = bp.NewRegenRate, - Mult = 1.0, - }, - }, - } + if self.RebuildingPod2 ~= nil then + RemoveEconomyEvent(self, self.RebuildingPod2) + self.RebuildingPod2 = nil end - Buff.ApplyBuff(self, 'UEFACUDamageStabilization') - elseif enh == 'DamageStabilizationRemove' then - if Buff.HasBuff(self, 'UEFACUDamageStabilization') then - Buff.RemoveBuff(self, 'UEFACUDamageStabilization') + end + KillThread(self.RebuildThread) + KillThread(self.RebuildThread2) + end, + + -- Teleport Upgrade + + ---@param self UEL0001 + ---@param bp Blueprint unused + ProcessEnhancementTeleporter = function(self, bp) + self:AddCommandCap('RULEUCC_Teleport') + end, + + ---@param self UEL0001 + ---@param bp Blueprint unused + ProcessEnhancementTeleporterRemove = function(self, bp) + self:RemoveCommandCap('RULEUCC_Teleport') + end, + + -- Personal Shield + + ---@param self UEL0001 + ---@param bp Blueprint + ProcessEnhancementShield = function(self, bp) + self:AddToggleCap('RULEUTC_ShieldToggle') + self:CreateShield(bp) + self:SetEnergyMaintenanceConsumptionOverride(bp.MaintenanceConsumptionPerSecondEnergy or 0) + self:SetMaintenanceConsumptionActive() + end, + + ---@param self UEL0001 + ---@param bp Blueprint unused + ProcessEnhancementShieldRemove = function(self, bp) + self:DestroyShield() + self:SetMaintenanceConsumptionInactive() + RemoveUnitEnhancement(self, 'ShieldRemove') + self:RemoveToggleCap('RULEUTC_ShieldToggle') + end, + + -- Bubble Shield + + ---@param self UEL0001 + ---@param bp Blueprint + ProcessEnhancementShieldGeneratorField = function(self, bp) + self:AddToggleCap('RULEUTC_ShieldToggle') + self:DestroyShield() + self:ForkThread( + function() + WaitTicks(1) + self:CreateShield(bp) + self:SetEnergyMaintenanceConsumptionOverride(bp.MaintenanceConsumptionPerSecondEnergy or 0) + self:SetMaintenanceConsumptionActive() end - elseif enh == 'HeavyAntiMatterCannon' then - local wep = self:GetWeaponByLabel('RightZephyr') - wep:AddDamageMod(bp.ZephyrDamageMod) - wep:ChangeMaxRadius(bp.NewMaxRadius or 44) - local oc = self:GetWeaponByLabel('OverCharge') - oc:ChangeMaxRadius(bp.NewMaxRadius or 44) - local aoc = self:GetWeaponByLabel('AutoOverCharge') - aoc:ChangeMaxRadius(bp.NewMaxRadius or 44) - elseif enh == 'HeavyAntiMatterCannonRemove' then - local bp = self:GetBlueprint().Enhancements['HeavyAntiMatterCannon'] - if not bp then return end - local wep = self:GetWeaponByLabel('RightZephyr') - wep:AddDamageMod(-bp.ZephyrDamageMod) - local bpDisrupt = self:GetBlueprint().Weapon[1].MaxRadius - wep:ChangeMaxRadius(bpDisrupt or 22) - local oc = self:GetWeaponByLabel('OverCharge') - oc:ChangeMaxRadius(bpDisrupt or 22) - local aoc = self:GetWeaponByLabel('AutoOverCharge') - aoc:ChangeMaxRadius(bpDisrupt or 22) - elseif enh == 'ResourceAllocation' then - local bp = self:GetBlueprint().Enhancements[enh] - local bpEcon = self:GetBlueprint().Economy - if not bp then return end - self:SetProductionPerSecondEnergy((bp.ProductionPerSecondEnergy + bpEcon.ProductionPerSecondEnergy) or 0) - self:SetProductionPerSecondMass((bp.ProductionPerSecondMass + bpEcon.ProductionPerSecondMass) or 0) - elseif enh == 'ResourceAllocationRemove' then - local bpEcon = self:GetBlueprint().Economy - self:SetProductionPerSecondEnergy(bpEcon.ProductionPerSecondEnergy or 0) - self:SetProductionPerSecondMass(bpEcon.ProductionPerSecondMass or 0) - elseif enh == 'TacticalMissile' then - self:AddCommandCap('RULEUCC_Tactical') - self:AddCommandCap('RULEUCC_SiloBuildTactical') - self:SetWeaponEnabledByLabel('TacMissile', true) - elseif enh == 'TacticalNukeMissile' then - self:RemoveCommandCap('RULEUCC_Tactical') - self:RemoveCommandCap('RULEUCC_SiloBuildTactical') - self:AddCommandCap('RULEUCC_Nuke') - self:AddCommandCap('RULEUCC_SiloBuildNuke') - self:SetWeaponEnabledByLabel('TacMissile', false) - self:SetWeaponEnabledByLabel('TacNukeMissile', true) - local amt = self:GetTacticalSiloAmmoCount() - self:RemoveTacticalSiloAmmo(amt or 0) - self:StopSiloBuild() - elseif enh == 'TacticalMissileRemove' or enh == 'TacticalNukeMissileRemove' then - self:RemoveCommandCap('RULEUCC_Nuke') - self:RemoveCommandCap('RULEUCC_SiloBuildNuke') - self:RemoveCommandCap('RULEUCC_Tactical') - self:RemoveCommandCap('RULEUCC_SiloBuildTactical') - self:SetWeaponEnabledByLabel('TacMissile', false) - self:SetWeaponEnabledByLabel('TacNukeMissile', false) - local amt = self:GetTacticalSiloAmmoCount() - self:RemoveTacticalSiloAmmo(amt or 0) - local amt = self:GetNukeSiloAmmoCount() - self:RemoveNukeSiloAmmo(amt or 0) - self:StopSiloBuild() + ) + end, + + ---@param self UEL0001 + ---@param bp Blueprint unused + ProcessEnhancementShieldGeneratorFieldRemove = function(self, bp) + self:DestroyShield() + self:SetMaintenanceConsumptionInactive() + self:RemoveToggleCap('RULEUTC_ShieldToggle') + end, + + -- T2 Engineering Suite + + ---@param self UEL0001 + ---@param bp Blueprint + ProcessEnhancementAdvancedEngineering = function(self, bp) + local cat = ParseEntityCategory(bp.BuildableCategoryAdds) + self:RemoveBuildRestriction(cat) + if not Buffs['UEFACUT2BuildRate'] then + BuffBlueprint { + Name = 'UEFACUT2BuildRate', + DisplayName = 'UEFACUT2BuildRate', + BuffType = 'ACUBUILDRATE', + Stacks = 'REPLACE', + Duration = -1, + Affects = { + BuildRate = { + Add = bp.NewBuildRate - self:GetBlueprint().Economy.BuildRate, + Mult = 1, + }, + MaxHealth = { + Add = bp.NewHealth, + Mult = 1.0, + }, + Regen = { + Add = bp.NewRegenRate, + Mult = 1.0, + }, + }, + } + end + Buff.ApplyBuff(self, 'UEFACUT2BuildRate') + + end, + + ---@param self UEL0001 + ---@param bp Blueprint unused + ProcessEnhancementAdvancedEngineeringRemove = function(self, bp) + local bp = self:GetBlueprint().Economy.BuildRate + if not bp then return end + self:RestoreBuildRestrictions() + self:AddBuildRestriction(categories.UEF * + (categories.BUILTBYTIER2COMMANDER + categories.BUILTBYTIER3COMMANDER)) + self:AddBuildRestriction(categories.UEF * + (categories.BUILTBYTIER2COMMANDER + categories.BUILTBYTIER3COMMANDER)) + if Buff.HasBuff(self, 'UEFACUT2BuildRate') then + Buff.RemoveBuff(self, 'UEFACUT2BuildRate') + end + end, + + -- T3 Engineering Suite + + ---@param self UEL0001 + ---@param bp Blueprint + ProcessEnhancementT3Engineering = function(self, bp) + local cat = ParseEntityCategory(bp.BuildableCategoryAdds) + self:RemoveBuildRestriction(cat) + if not Buffs['UEFACUT3BuildRate'] then + BuffBlueprint { + Name = 'UEFACUT3BuildRate', + DisplayName = 'UEFCUT3BuildRate', + BuffType = 'ACUBUILDRATE', + Stacks = 'REPLACE', + Duration = -1, + Affects = { + BuildRate = { + Add = bp.NewBuildRate - self:GetBlueprint().Economy.BuildRate, + Mult = 1, + }, + MaxHealth = { + Add = bp.NewHealth, + Mult = 1.0, + }, + Regen = { + Add = bp.NewRegenRate, + Mult = 1.0, + }, + }, + } + end + Buff.ApplyBuff(self, 'UEFACUT3BuildRate') + + end, + + ---@param self UEL0001 + ---@param bp Blueprint unused + ProcessEnhancementT3EngineeringRemove = function(self, bp) + local bp = self:GetBlueprint().Economy.BuildRate + if not bp then return end + self:RestoreBuildRestrictions() + if Buff.HasBuff(self, 'UEFACUT3BuildRate') then + Buff.RemoveBuff(self, 'UEFACUT3BuildRate') + end + self:AddBuildRestriction(categories.UEF * (categories.BUILTBYTIER2COMMANDER + categories.BUILTBYTIER3COMMANDER)) + end, + + -- Nano Repair System + + ---@param self UEL0001 + ---@param bp Blueprint + ProcessEnhancementDamageStabilization = function(self, bp) + if not Buffs['UEFACUDamageStabilization'] then + BuffBlueprint { + Name = 'UEFACUDamageStabilization', + DisplayName = 'UEFACUDamageStabilization', + BuffType = 'DamageStabilization', + Stacks = 'REPLACE', + Duration = -1, + Affects = { + MaxHealth = { + Add = bp.NewHealth, + Mult = 1.0, + }, + Regen = { + Add = bp.NewRegenRate, + Mult = 1.0, + }, + }, + } + end + Buff.ApplyBuff(self, 'UEFACUDamageStabilization') + + end, + + ---@param self UEL0001 + ---@param bp Blueprint unused + ProcessEnhancementDamageStabilizationRemove = function(self, bp) + if Buff.HasBuff(self, 'UEFACUDamageStabilization') then + Buff.RemoveBuff(self, 'UEFACUDamageStabilization') + end + end, + + -- Gun Upgrade + + ---@param self UEL0001 + ---@param bp Blueprint + ProcessEnhancementHeavyAntiMatterCannon = function(self, bp) + local wep = self:GetWeaponByLabel('RightZephyr') + wep:AddDamageMod(bp.ZephyrDamageMod) + wep:ChangeMaxRadius(bp.NewMaxRadius or 44) + local oc = self:GetWeaponByLabel('OverCharge') + oc:ChangeMaxRadius(bp.NewMaxRadius or 44) + local aoc = self:GetWeaponByLabel('AutoOverCharge') + aoc:ChangeMaxRadius(bp.NewMaxRadius or 44) + end, + + ---@param self UEL0001 + ---@param bp Blueprint unused + ProcessEnhancementHeavyAntiMatterCannonRemove = function(self, bp) + local bp = self:GetBlueprint().Enhancements['HeavyAntiMatterCannon'] + if not bp then return end + local wep = self:GetWeaponByLabel('RightZephyr') + wep:AddDamageMod(-bp.ZephyrDamageMod) + local bpDisrupt = self:GetBlueprint().Weapon[1].MaxRadius + wep:ChangeMaxRadius(bpDisrupt or 22) + local oc = self:GetWeaponByLabel('OverCharge') + oc:ChangeMaxRadius(bpDisrupt or 22) + local aoc = self:GetWeaponByLabel('AutoOverCharge') + aoc:ChangeMaxRadius(bpDisrupt or 22) + end, + + -- RAS + + ---@param self UEL0001 + ---@param bp Blueprint + ProcessEnhancementResourceAllocation = function(self, bp) + local bpEcon = self:GetBlueprint().Economy + if not bp then return end + self:SetProductionPerSecondEnergy((bp.ProductionPerSecondEnergy + bpEcon.ProductionPerSecondEnergy) or 0) + self:SetProductionPerSecondMass((bp.ProductionPerSecondMass + bpEcon.ProductionPerSecondMass) or 0) + + end, + + ---@param self UEL0001 + ---@param bp Blueprint unused + ProcessEnhancementResourceAllocationRemove = function(self, bp) + local bpEcon = self:GetBlueprint().Economy + self:SetProductionPerSecondEnergy(bpEcon.ProductionPerSecondEnergy or 0) + self:SetProductionPerSecondMass(bpEcon.ProductionPerSecondMass or 0) + end, + + -- Tactical Missile Launcher + + ---@param self UEL0001 + ---@param bp Blueprint unused + ProcessEnhancementTacticalMissile = function(self, bp) + self:AddCommandCap('RULEUCC_Tactical') + self:AddCommandCap('RULEUCC_SiloBuildTactical') + self:SetWeaponEnabledByLabel('TacMissile', true) + end, + + ---@param self UEL0001 + ---@param bp Blueprint unused + ProcessEnhancementTacticalMissileRemove = function(self, bp) + self:RemoveCommandCap('RULEUCC_Tactical') + self:RemoveCommandCap('RULEUCC_SiloBuildTactical') + self:SetWeaponEnabledByLabel('TacMissile', false) + local amt = self:GetTacticalSiloAmmoCount() + self:RemoveTacticalSiloAmmo(amt or 0) + self:StopSiloBuild() + end, + + -- Billy Nuke + + ---@param self UEL0001 + ---@param bp Blueprint unused + ProcessEnhancementTacticalNukeMissile = function(self, bp) + self:RemoveCommandCap('RULEUCC_Tactical') + self:RemoveCommandCap('RULEUCC_SiloBuildTactical') + self:AddCommandCap('RULEUCC_Nuke') + self:AddCommandCap('RULEUCC_SiloBuildNuke') + self:SetWeaponEnabledByLabel('TacMissile', false) + self:SetWeaponEnabledByLabel('TacNukeMissile', true) + local amt = self:GetTacticalSiloAmmoCount() + self:RemoveTacticalSiloAmmo(amt or 0) + self:StopSiloBuild() + end, + + ---@param self UEL0001 + ---@param bp Blueprint unused + ProcessEnhancementTacticalNukeMissileRemove = function(self, bp) + self:RemoveCommandCap('RULEUCC_Nuke') + self:RemoveCommandCap('RULEUCC_SiloBuildNuke') + self:RemoveCommandCap('RULEUCC_Tactical') + self:RemoveCommandCap('RULEUCC_SiloBuildTactical') + self:SetWeaponEnabledByLabel('TacMissile', false) + self:SetWeaponEnabledByLabel('TacNukeMissile', false) + local amt = self:GetTacticalSiloAmmoCount() + self:RemoveTacticalSiloAmmo(amt or 0) + local amt = self:GetNukeSiloAmmoCount() + self:RemoveNukeSiloAmmo(amt or 0) + self:StopSiloBuild() + end, + + + ---@param self UEL0001 + ---@param enh string + CreateEnhancement = function(self, enh) + ACUUnit.CreateEnhancement(self, enh) + + local bp = self:GetBlueprint().Enhancements[enh] + if not bp then return end + + local ref = 'ProcessEnhancement' .. enh + local handler = self[ref] + if handler then + handler(self, bp) + else + WARN("Missing enhancement: ", enh, " for unit: ", self:GetUnitId(), " note that the function name should be called: ", ref) end end, + + --#endregion } TypeClass = UEL0001 From 3f034d13505450b406994746590d6668ed322826 Mon Sep 17 00:00:00 2001 From: Basilisk3 <126026384+Basilisk3@users.noreply.github.com> Date: Thu, 31 Oct 2024 11:30:57 +0100 Subject: [PATCH 03/33] Fix a crash when the `EnableDiskWatch` command line argument is enabled (#6494) --- changelog/snippets/fix.6494.md | 1 + lua/system/blueprints-units.lua | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 changelog/snippets/fix.6494.md diff --git a/changelog/snippets/fix.6494.md b/changelog/snippets/fix.6494.md new file mode 100644 index 0000000000..89b2cdbff1 --- /dev/null +++ b/changelog/snippets/fix.6494.md @@ -0,0 +1 @@ +- (#6494) Fix a crash when the `EnableDiskWatch` command line argument is enabled. diff --git a/lua/system/blueprints-units.lua b/lua/system/blueprints-units.lua index 8b311fb23a..0c9fd3bdd4 100644 --- a/lua/system/blueprints-units.lua +++ b/lua/system/blueprints-units.lua @@ -397,7 +397,7 @@ local function PostProcessUnit(unit) status.AllIntel = {} if intelBlueprint then for name, value in pairs(intelBlueprint) do - + -- may contain tables, such as `JamRadius` if type(value) ~= 'table' then if value == true or value > 0 then @@ -555,7 +555,7 @@ local function PostProcessUnit(unit) -- Define a specific TransportSpeedReduction for all land and naval units. -- Experimentals have a TransportSpeedReduction of 1 due to transports gaining 1 speed and some survival maps loading experimentals into transports. -- Naval units also gain a TransportSpeedReduction of 1 to ensure mod compatibility. - if not unit.Physics.TransportSpeedReduction and not isStructure then + if unit.Physics and not unit.Physics.TransportSpeedReduction and not isStructure then if isLand and isTech1 then unit.Physics.TransportSpeedReduction = 0.15 elseif isLand and isTech2 then From 1491ad6fec3b570533153771ccabbb32f066a534 Mon Sep 17 00:00:00 2001 From: Josh Date: Wed, 6 Nov 2024 02:46:18 -0800 Subject: [PATCH 04/33] Refactor Aeon ACU Enhancements Script (#6502) Continuing the refactor of #6498 --------- Co-authored-by: lL1l1 <82986251+lL1l1@users.noreply.github.com> --- changelog/snippets/other.6498.md | 2 +- units/UAL0001/UAL0001_script.lua | 498 ++++++++++++++++++------------- 2 files changed, 299 insertions(+), 201 deletions(-) diff --git a/changelog/snippets/other.6498.md b/changelog/snippets/other.6498.md index 6387b72138..237a2857b1 100644 --- a/changelog/snippets/other.6498.md +++ b/changelog/snippets/other.6498.md @@ -1 +1 @@ -- (#6498) Refactor the Enhancements section in the Unit script file to replace the long if/else chain with a more modular design. Each enhancement should have its own dedicated function, enabling cleaner code organization and easier maintenance. The CreateEnhancement function will then call the appropriate enhancement function based on specific enhancement criteria. +- (#6498, #6502) Refactor the Enhancements section in the ACU/SACU scripts to replace the long if/else chain with a more modular design that is easier to maintain and hook. Each enhancement has its own dedicated function, named with the format `ProcessEnhancement[EnhancementName]`. The CreateEnhancement function now calls the appropriate enhancement function automatically by that name format. diff --git a/units/UAL0001/UAL0001_script.lua b/units/UAL0001/UAL0001_script.lua index fcbdc7e5cd..8070c43ae8 100644 --- a/units/UAL0001/UAL0001_script.lua +++ b/units/UAL0001/UAL0001_script.lua @@ -49,7 +49,7 @@ UAL0001 = ClassUnit(ACUUnit) { self:HideBone('Right_Upgrade', true) self:HideBone('Left_Upgrade', true) -- Set initial range of Chrono here so that max range can be displayed in the UI - local bpDisrupt = self:GetBlueprint().Weapon[1].MaxRadius + local bpDisrupt = self.Blueprint.Weapon[1].MaxRadius local cd = self:GetWeaponByLabel('ChronoDampener') cd:ChangeMaxRadius(bpDisrupt) -- Restrict what enhancements will enable later @@ -67,218 +67,316 @@ UAL0001 = ClassUnit(ACUUnit) { EffectUtil.CreateAeonCommanderBuildingEffects(self, unitBeingBuilt, self.BuildEffectBones, self.BuildEffectsBag) end, - CreateEnhancement = function(self, enh) - ACUUnit.CreateEnhancement(self, enh) - local bp = self:GetBlueprint().Enhancements[enh] - -- Resource Allocation - if enh == 'ResourceAllocation' then - local bp = self:GetBlueprint().Enhancements[enh] - local bpEcon = self:GetBlueprint().Economy - if not bp then return end - self:SetProductionPerSecondEnergy((bp.ProductionPerSecondEnergy + bpEcon.ProductionPerSecondEnergy) or 0) - self:SetProductionPerSecondMass((bp.ProductionPerSecondMass + bpEcon.ProductionPerSecondMass) or 0) - elseif enh == 'ResourceAllocationRemove' then - local bpEcon = self:GetBlueprint().Economy - self:SetProductionPerSecondEnergy(bpEcon.ProductionPerSecondEnergy or 0) - self:SetProductionPerSecondMass(bpEcon.ProductionPerSecondMass or 0) - elseif enh == 'ResourceAllocationAdvanced' then - local bp = self:GetBlueprint().Enhancements[enh] - local bpEcon = self:GetBlueprint().Economy - if not bp then return end - self:SetProductionPerSecondEnergy((bp.ProductionPerSecondEnergy + bpEcon.ProductionPerSecondEnergy) or 0) - self:SetProductionPerSecondMass((bp.ProductionPerSecondMass + bpEcon.ProductionPerSecondMass) or 0) - elseif enh == 'ResourceAllocationAdvancedRemove' then - local bpEcon = self:GetBlueprint().Economy - self:SetProductionPerSecondEnergy(bpEcon.ProductionPerSecondEnergy or 0) - self:SetProductionPerSecondMass(bpEcon.ProductionPerSecondMass or 0) - -- Shields - elseif enh == 'Shield' then - self:AddToggleCap('RULEUTC_ShieldToggle') - self:SetEnergyMaintenanceConsumptionOverride(bp.MaintenanceConsumptionPerSecondEnergy or 0) - self:SetMaintenanceConsumptionActive() - self:CreateShield(bp) - elseif enh == 'ShieldRemove' then - self:DestroyShield() - self:SetMaintenanceConsumptionInactive() - self:RemoveToggleCap('RULEUTC_ShieldToggle') - elseif enh == 'ShieldHeavy' then - self:AddToggleCap('RULEUTC_ShieldToggle') - self:ForkThread(self.CreateHeavyShield, bp) - elseif enh == 'ShieldHeavyRemove' then - self:DestroyShield() - self:SetMaintenanceConsumptionInactive() - self:RemoveToggleCap('RULEUTC_ShieldToggle') - -- Teleporter - elseif enh == 'Teleporter' then - self:AddCommandCap('RULEUCC_Teleport') - elseif enh == 'TeleporterRemove' then - self:RemoveCommandCap('RULEUCC_Teleport') - -- Chrono Dampener - elseif enh == 'ChronoDampener' then - self:SetWeaponEnabledByLabel('ChronoDampener', true) - if not Buffs['AeonACUChronoDampener'] then - BuffBlueprint { - Name = 'AeonACUChronoDampener', - DisplayName = 'AeonACUChronoDampener', - BuffType = 'DamageStabilization', - Stacks = 'REPLACE', - Duration = -1, - Affects = { - MaxHealth = { - Add = bp.NewHealth, - Mult = 1.0, - }, + + --------------------------------------------------------------------------- + --#region Enhancements + + ---@param self UAL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementResourceAllocation = function(self, bp) + if not bp then return end + local bpEcon = self.Blueprint.Economy + self:SetProductionPerSecondEnergy((bp.ProductionPerSecondEnergy + bpEcon.ProductionPerSecondEnergy) or 0) + self:SetProductionPerSecondMass((bp.ProductionPerSecondMass + bpEcon.ProductionPerSecondMass) or 0) + end, + + ---@param self UAL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementResourceAllocationRemove = function(self, bp) + local bpEcon = self.Blueprint.Economy + self:SetProductionPerSecondEnergy(bpEcon.ProductionPerSecondEnergy or 0) + self:SetProductionPerSecondMass(bpEcon.ProductionPerSecondMass or 0) + end, + + ---@param self UAL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementResourceAllocationAdvanced = function(self, bp) + if not bp then return end + local bpEcon = self.Blueprint.Economy + self:SetProductionPerSecondEnergy((bp.ProductionPerSecondEnergy + bpEcon.ProductionPerSecondEnergy) or 0) + self:SetProductionPerSecondMass((bp.ProductionPerSecondMass + bpEcon.ProductionPerSecondMass) or 0) + end, + + ---@param self UAL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementResourceAllocationAdvancedRemove = function(self, bp) + local bpEcon = self.Blueprint.Economy + self:SetProductionPerSecondEnergy(bpEcon.ProductionPerSecondEnergy or 0) + self:SetProductionPerSecondMass(bpEcon.ProductionPerSecondMass or 0) + end, + + ---@param self UAL0001 + ---@param bp UnitBlueprintEnhancement # This enhancement blueprint also includes the fields from `UnitBlueprintDefenseShield` + ProcessEnhancementShield = function(self, bp) + self:AddToggleCap('RULEUTC_ShieldToggle') + self:SetEnergyMaintenanceConsumptionOverride(bp.MaintenanceConsumptionPerSecondEnergy or 0) + self:SetMaintenanceConsumptionActive() + self:CreateShield(bp) + end, + + ---@param self UAL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementShieldRemove = function(self, bp) + self:DestroyShield() + self:SetMaintenanceConsumptionInactive() + self:RemoveToggleCap('RULEUTC_ShieldToggle') + end, + + ---@param self UAL0001 + ---@param bp UnitBlueprintEnhancement # This enhancement blueprint also includes the fields from `UnitBlueprintDefenseShield` + ProcessEnhancementShieldHeavy = function(self, bp) + self:AddToggleCap('RULEUTC_ShieldToggle') + self:ForkThread(self.CreateHeavyShield, bp) + end, + + ---@param self UAL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementShieldHeavyRemove = function(self, bp) + self:DestroyShield() + self:SetMaintenanceConsumptionInactive() + self:RemoveToggleCap('RULEUTC_ShieldToggle') + end, + + ---@param self UAL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementTeleporter = function(self, bp) + self:AddCommandCap('RULEUCC_Teleport') + end, + + ---@param self UAL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementTeleporterRemove = function(self, bp) + self:RemoveCommandCap('RULEUCC_Teleport') + end, + + ---@param self UAL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementChronoDampener = function(self, bp) + self:SetWeaponEnabledByLabel('ChronoDampener', true) + if not Buffs['AeonACUChronoDampener'] then + BuffBlueprint { + Name = 'AeonACUChronoDampener', + DisplayName = 'AeonACUChronoDampener', + BuffType = 'DamageStabilization', + Stacks = 'REPLACE', + Duration = -1, + Affects = { + MaxHealth = { + Add = bp.NewHealth, + Mult = 1.0, }, - } - end - Buff.ApplyBuff(self, 'AeonACUChronoDampener') - elseif enh == 'ChronoDampenerRemove' then - if Buff.HasBuff(self, 'AeonACUChronoDampener') then - Buff.RemoveBuff(self, 'AeonACUChronoDampener') - end - self:SetWeaponEnabledByLabel('ChronoDampener', false) - -- T2 Engineering - elseif enh =='AdvancedEngineering' then - local bp = self:GetBlueprint().Enhancements[enh] - if not bp then return end - local cat = ParseEntityCategory(bp.BuildableCategoryAdds) - self:RemoveBuildRestriction(cat) + }, + } + end + Buff.ApplyBuff(self, 'AeonACUChronoDampener') + end, + + ---@param self UAL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementChronoDampenerRemove = function(self, bp) + if Buff.HasBuff(self, 'AeonACUChronoDampener') then + Buff.RemoveBuff(self, 'AeonACUChronoDampener') + end + self:SetWeaponEnabledByLabel('ChronoDampener', false) + end, + + ---@param self UAL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementAdvancedEngineering = function(self, bp) + if not bp then return end + local cat = ParseEntityCategory(bp.BuildableCategoryAdds) + self:RemoveBuildRestriction(cat) if not Buffs['AeonACUT2BuildRate'] then - BuffBlueprint { - Name = 'AeonACUT2BuildRate', - DisplayName = 'AeonACUT2BuildRate', - BuffType = 'ACUBUILDRATE', - Stacks = 'REPLACE', - Duration = -1, - Affects = { - BuildRate = { - Add = bp.NewBuildRate - self:GetBlueprint().Economy.BuildRate, - Mult = 1, - }, - MaxHealth = { - Add = bp.NewHealth, - Mult = 1.0, - }, - Regen = { - Add = bp.NewRegenRate, - Mult = 1.0, - }, + BuffBlueprint { + Name = 'AeonACUT2BuildRate', + DisplayName = 'AeonACUT2BuildRate', + BuffType = 'ACUBUILDRATE', + Stacks = 'REPLACE', + Duration = -1, + Affects = { + BuildRate = { + Add = bp.NewBuildRate - self.Blueprint.Economy.BuildRate, + Mult = 1, + }, + MaxHealth = { + Add = bp.NewHealth, + Mult = 1.0, + }, + Regen = { + Add = bp.NewRegenRate, + Mult = 1.0, + }, + }, + } + end + Buff.ApplyBuff(self, 'AeonACUT2BuildRate') + end, + + ---@param self UAL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementAdvancedEngineeringRemove = function(self, bp) + self:RestoreBuildRestrictions() + self:AddBuildRestriction(categories.AEON * (categories.BUILTBYTIER2COMMANDER + categories.BUILTBYTIER3COMMANDER)) + if Buff.HasBuff(self, 'AeonACUT2BuildRate') then + Buff.RemoveBuff(self, 'AeonACUT2BuildRate') + end + end, + + ---@param self UAL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementT3Engineering = function(self, bp) + if not bp then return end + local cat = ParseEntityCategory(bp.BuildableCategoryAdds) + self:RemoveBuildRestriction(cat) + if not Buffs['AeonACUT3BuildRate'] then + BuffBlueprint { + Name = 'AeonACUT3BuildRate', + DisplayName = 'AeonCUT3BuildRate', + BuffType = 'ACUBUILDRATE', + Stacks = 'REPLACE', + Duration = -1, + Affects = { + BuildRate = { + Add = bp.NewBuildRate - self.Blueprint.Economy.BuildRate, + Mult = 1, + }, + MaxHealth = { + Add = bp.NewHealth, + Mult = 1.0, }, - } - end - Buff.ApplyBuff(self, 'AeonACUT2BuildRate') - elseif enh =='AdvancedEngineeringRemove' then - local bp = self:GetBlueprint().Economy.BuildRate - if not bp then return end - self:RestoreBuildRestrictions() - self:AddBuildRestriction(categories.AEON * (categories.BUILTBYTIER2COMMANDER + categories.BUILTBYTIER3COMMANDER)) - if Buff.HasBuff(self, 'AeonACUT2BuildRate') then - Buff.RemoveBuff(self, 'AeonACUT2BuildRate') - end - -- T3 Engineering - elseif enh =='T3Engineering' then - local bp = self:GetBlueprint().Enhancements[enh] - if not bp then return end - local cat = ParseEntityCategory(bp.BuildableCategoryAdds) - self:RemoveBuildRestriction(cat) - if not Buffs['AeonACUT3BuildRate'] then - BuffBlueprint { - Name = 'AeonACUT3BuildRate', - DisplayName = 'AeonCUT3BuildRate', - BuffType = 'ACUBUILDRATE', - Stacks = 'REPLACE', - Duration = -1, - Affects = { - BuildRate = { - Add = bp.NewBuildRate - self:GetBlueprint().Economy.BuildRate, - Mult = 1, - }, - MaxHealth = { - Add = bp.NewHealth, - Mult = 1.0, - }, - Regen = { - Add = bp.NewRegenRate, - Mult = 1.0, - }, + Regen = { + Add = bp.NewRegenRate, + Mult = 1.0, }, - } - end - Buff.ApplyBuff(self, 'AeonACUT3BuildRate') - elseif enh =='T3EngineeringRemove' then - local bp = self:GetBlueprint().Economy.BuildRate - if not bp then return end - self:RestoreBuildRestrictions() - self:AddBuildRestriction(categories.AEON * (categories.BUILTBYTIER2COMMANDER + categories.BUILTBYTIER3COMMANDER)) - if Buff.HasBuff(self, 'AeonACUT3BuildRate') then - Buff.RemoveBuff(self, 'AeonACUT3BuildRate') - end - -- Crysalis Beam - elseif enh == 'CrysalisBeam' then - local wep = self:GetWeaponByLabel('RightDisruptor') - wep:ChangeMaxRadius(bp.NewMaxRadius or 30) - local oc = self:GetWeaponByLabel('OverCharge') - oc:ChangeMaxRadius(bp.NewMaxRadius or 30) - local aoc = self:GetWeaponByLabel('AutoOverCharge') - aoc:ChangeMaxRadius(bp.NewMaxRadius or 30) - local cd = self:GetWeaponByLabel('ChronoDampener') - cd:ChangeMaxRadius(bp.NewMaxRadius or 30) - elseif enh == 'CrysalisBeamRemove' then - local wep = self:GetWeaponByLabel('RightDisruptor') - local bpDisrupt = self:GetBlueprint().Weapon[1].MaxRadius - wep:ChangeMaxRadius(bpDisrupt or 22) - local oc = self:GetWeaponByLabel('OverCharge') - oc:ChangeMaxRadius(bpDisrupt or 22) - local aoc = self:GetWeaponByLabel('AutoOverCharge') - aoc:ChangeMaxRadius(bpDisrupt or 22) - local cd = self:GetWeaponByLabel('ChronoDampener') - cd:ChangeMaxRadius(bpDisrupt or 22) - -- Advanced Cryslised Beam - elseif enh == 'FAF_CrysalisBeamAdvanced' then - local wep = self:GetWeaponByLabel('RightDisruptor') - wep:ChangeMaxRadius(bp.NewMaxRadius or 35) - local oc = self:GetWeaponByLabel('OverCharge') - oc:ChangeMaxRadius(bp.NewMaxRadius or 35) - local aoc = self:GetWeaponByLabel('AutoOverCharge') - aoc:ChangeMaxRadius(bp.NewMaxRadius or 35) - local cd = self:GetWeaponByLabel('ChronoDampener') - cd:ChangeMaxRadius(bp.NewMaxRadius or 35) - elseif enh == 'FAF_CrysalisBeamAdvancedRemove' then - local wep = self:GetWeaponByLabel('RightDisruptor') - local bpDisrupt = self:GetBlueprint().Weapon[1].MaxRadius - wep:ChangeMaxRadius(bpDisrupt or 22) - local oc = self:GetWeaponByLabel('OverCharge') - oc:ChangeMaxRadius(bpDisrupt or 22) - local aoc = self:GetWeaponByLabel('AutoOverCharge') - aoc:ChangeMaxRadius(bpDisrupt or 22) - local cd = self:GetWeaponByLabel('ChronoDampener') - cd:ChangeMaxRadius(bpDisrupt or 22) - -- Heat Sink Augmentation - elseif enh == 'HeatSink' then - local wep = self:GetWeaponByLabel('RightDisruptor') - wep:ChangeRateOfFire(bp.NewRateOfFire or 2) - elseif enh == 'HeatSinkRemove' then - local wep = self:GetWeaponByLabel('RightDisruptor') - local bpDisrupt = self:GetBlueprint().Weapon[1].RateOfFire - wep:ChangeRateOfFire(bpDisrupt or 1) - -- Enhanced Sensor Systems - elseif enh == 'EnhancedSensors' then - self:SetIntelRadius('Vision', bp.NewVisionRadius or 104) - self:SetIntelRadius('Omni', bp.NewOmniRadius or 104) - elseif enh == 'EnhancedSensorsRemove' then - local bpIntel = self:GetBlueprint().Intel - self:SetIntelRadius('Vision', bpIntel.VisionRadius or 26) - self:SetIntelRadius('Omni', bpIntel.OmniRadius or 26) - end + }, + } + end + Buff.ApplyBuff(self, 'AeonACUT3BuildRate') + end, + + ---@param self UAL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementT3EngineeringRemove = function(self, bp) + self:RestoreBuildRestrictions() + self:AddBuildRestriction(categories.AEON * (categories.BUILTBYTIER2COMMANDER + categories.BUILTBYTIER3COMMANDER)) + if Buff.HasBuff(self, 'AeonACUT3BuildRate') then + Buff.RemoveBuff(self, 'AeonACUT3BuildRate') + end + end, + + ---@param self UAL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementCrysalisBeam = function(self, bp) + local wep = self:GetWeaponByLabel('RightDisruptor') + wep:ChangeMaxRadius(bp.NewMaxRadius or 30) + local oc = self:GetWeaponByLabel('OverCharge') + oc:ChangeMaxRadius(bp.NewMaxRadius or 30) + local aoc = self:GetWeaponByLabel('AutoOverCharge') + aoc:ChangeMaxRadius(bp.NewMaxRadius or 30) + local cd = self:GetWeaponByLabel('ChronoDampener') + cd:ChangeMaxRadius(bp.NewMaxRadius or 30) + end, + + ---@param self UAL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementCrysalisBeamRemove = function(self, bp) + local wep = self:GetWeaponByLabel('RightDisruptor') + local bpDisrupt = self.Blueprint.Weapon[1].MaxRadius + wep:ChangeMaxRadius(bpDisrupt or 22) + local oc = self:GetWeaponByLabel('OverCharge') + oc:ChangeMaxRadius(bpDisrupt or 22) + local aoc = self:GetWeaponByLabel('AutoOverCharge') + aoc:ChangeMaxRadius(bpDisrupt or 22) + local cd = self:GetWeaponByLabel('ChronoDampener') + cd:ChangeMaxRadius(bpDisrupt or 22) + end, + + ---@param self UAL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementFAF_CrysalisBeamAdvanced = function(self, bp) + local wep = self:GetWeaponByLabel('RightDisruptor') + wep:ChangeMaxRadius(bp.NewMaxRadius or 35) + local oc = self:GetWeaponByLabel('OverCharge') + oc:ChangeMaxRadius(bp.NewMaxRadius or 35) + local aoc = self:GetWeaponByLabel('AutoOverCharge') + aoc:ChangeMaxRadius(bp.NewMaxRadius or 35) + local cd = self:GetWeaponByLabel('ChronoDampener') + cd:ChangeMaxRadius(bp.NewMaxRadius or 35) + end, + + ---@param self UAL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementFAF_CrysalisBeamAdvancedRemove = function(self, bp) + local wep = self:GetWeaponByLabel('RightDisruptor') + local bpDisrupt = self.Blueprint.Weapon[1].MaxRadius + wep:ChangeMaxRadius(bpDisrupt or 22) + local oc = self:GetWeaponByLabel('OverCharge') + oc:ChangeMaxRadius(bpDisrupt or 22) + local aoc = self:GetWeaponByLabel('AutoOverCharge') + aoc:ChangeMaxRadius(bpDisrupt or 22) + local cd = self:GetWeaponByLabel('ChronoDampener') + cd:ChangeMaxRadius(bpDisrupt or 22) + end, + + ---@param self UAL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementHeatSink = function(self, bp) + local wep = self:GetWeaponByLabel('RightDisruptor') + wep:ChangeRateOfFire(bp.NewRateOfFire or 2) + end, + + ---@param self UAL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementHeatSinkRemove = function(self, bp) + local wep = self:GetWeaponByLabel('RightDisruptor') + local bpDisrupt = self.Blueprint.Weapon[1].RateOfFire + wep:ChangeRateOfFire(bpDisrupt or 1) + end, + + ---@param self UAL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementEnhancedSensors = function(self, bp) + self:SetIntelRadius('Vision', bp.NewVisionRadius or 104) + self:SetIntelRadius('Omni', bp.NewOmniRadius or 104) + end, + + ---@param self UAL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementEnhancedSensorsRemove = function(self, bp) + local bpIntel = self.Blueprint.Intel + self:SetIntelRadius('Vision', bpIntel.VisionRadius or 26) + self:SetIntelRadius('Omni', bpIntel.OmniRadius or 26) end, + ---@param self UAL0001 + ---@param enh Enhancement + CreateEnhancement = function(self, enh) + ACUUnit.CreateEnhancement(self, enh) + + local bp = self.Blueprint.Enhancements[enh] + + if not bp then return end + + local ref = 'ProcessEnhancement' .. enh + local handler = self[ref] + if handler then + handler(self, bp) + else + WARN("Missing enhancement: ", enh, " for unit: ", self:GetUnitId(), " note that the function name should be called: ", ref) + end + end, + + ---@param self UAL0001 + ---@param bp UnitBlueprintEnhancement # This enhancement blueprint also includes the fields from `UnitBlueprintDefenseShield` CreateHeavyShield = function(self, bp) WaitTicks(1) self:CreateShield(bp) self:SetEnergyMaintenanceConsumptionOverride(bp.MaintenanceConsumptionPerSecondEnergy or 0) self:SetMaintenanceConsumptionActive() end + + --#endregion } TypeClass = UAL0001 From 5a55468f50012ed37b00de96dfdc8f55afb4220c Mon Sep 17 00:00:00 2001 From: lL1l1 <82986251+lL1l1@users.noreply.github.com> Date: Thu, 7 Nov 2024 00:09:23 -0800 Subject: [PATCH 05/33] Fix Ythotha being able to move backwards (#6466) --- changelog/snippets/balance.6466.md | 2 ++ engine/Core/Blueprints/UnitBlueprint.lua | 2 +- units/XSL0401/XSL0401_unit.bp | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 changelog/snippets/balance.6466.md diff --git a/changelog/snippets/balance.6466.md b/changelog/snippets/balance.6466.md new file mode 100644 index 0000000000..cc267ef7ca --- /dev/null +++ b/changelog/snippets/balance.6466.md @@ -0,0 +1,2 @@ +- (#6466) Fix Ythotha being able to walk backwards. + - `MaxSpeedReverse`: 2.5 -> 0 diff --git a/engine/Core/Blueprints/UnitBlueprint.lua b/engine/Core/Blueprints/UnitBlueprint.lua index af1e1d7fde..4d3783ec71 100644 --- a/engine/Core/Blueprints/UnitBlueprint.lua +++ b/engine/Core/Blueprints/UnitBlueprint.lua @@ -1119,7 +1119,7 @@ ---@field MaxGroundVariation number --- maximum speed for the unit ---@field MaxSpeed number ---- maximum speed for the unit in reverse +--- maximum speed for the unit in reverse. Defaults to the same value as MaxSpeed ---@field MaxSpeedReverse number --- maximum steer force magnitude that can be applied to acceleration ---@field MaxSteerForce number diff --git a/units/XSL0401/XSL0401_unit.bp b/units/XSL0401/XSL0401_unit.bp index 7cd5195ba6..a6e9d90112 100644 --- a/units/XSL0401/XSL0401_unit.bp +++ b/units/XSL0401/XSL0401_unit.bp @@ -195,6 +195,7 @@ UnitBlueprint{ MaxAcceleration = 2.5, MaxBrake = 2.5, MaxSpeed = 2.5, + MaxSpeedReverse = 0, MaxSteerForce = 10, MeshExtentsX = 2.75, MeshExtentsY = 6.75, From c69792cf56cf1933d76e98a262f44a5bbcefd5b8 Mon Sep 17 00:00:00 2001 From: Alexander <84857900+4z0t@users.noreply.github.com> Date: Thu, 7 Nov 2024 14:04:39 +0300 Subject: [PATCH 06/33] Generalize behavior of Salem that allows it to switch to land (#6499) --- changelog/snippets/features.6499.md | 5 +++++ changelog/snippets/fix.6499.md | 5 +++++ engine/Patches.lua | 14 -------------- engine/Sim/Unit.lua | 6 +++++- units/URS0201/URS0201_script.lua | 4 ++-- units/URS0201/URS0201_unit.bp | 7 +++++++ 6 files changed, 24 insertions(+), 17 deletions(-) create mode 100644 changelog/snippets/features.6499.md create mode 100644 changelog/snippets/fix.6499.md delete mode 100644 engine/Patches.lua diff --git a/changelog/snippets/features.6499.md b/changelog/snippets/features.6499.md new file mode 100644 index 0000000000..0aae6dbb01 --- /dev/null +++ b/changelog/snippets/features.6499.md @@ -0,0 +1,5 @@ +- (#6499) Allow Footprint locking for (modded) units + +With thanks to an assembly patch the footprint changes that were previously unique to the Cybran Tech 2 Destroyer can now be applied to any unit. For more details, see the pull request on GitHub. + +Interested in how assembly patches work? Reach out to the game team. You can find us easiest via the official Discord server. \ No newline at end of file diff --git a/changelog/snippets/fix.6499.md b/changelog/snippets/fix.6499.md new file mode 100644 index 0000000000..400aebf8de --- /dev/null +++ b/changelog/snippets/fix.6499.md @@ -0,0 +1,5 @@ +- (#6499) Fix hard crash when exiting the game when a Salem is on land + +The Cybran Tech 2 Destroyer (Salem) has the special ability to move from water onto land. There was a bug in a special, alternative implementation introduced by FAForever. Together with an assembly patch this bug is now fixed. + +Interested in how assembly patches work? Reach out to the game team. You can find us easiest via the official Discord server. \ No newline at end of file diff --git a/engine/Patches.lua b/engine/Patches.lua deleted file mode 100644 index acbddd433b..0000000000 --- a/engine/Patches.lua +++ /dev/null @@ -1,14 +0,0 @@ --- Info about exe patches and new lua<->engine functions - --- sim version of GetStat(). Awesome function as it already sends a string, integer and unit object to engine + --- returns a table with values. This is basically all you need for lua<->engine communication. --- --- "h1_SetSalemAmph" 1/0 controls amhibious mode for Salem destroyer. It takes unit's C-object and changes bp pointer --- to a copy of urs0201 bp with bp.Footprint.MinWaterDepth = 1.5 and bp.Footprint.OccupancyCaps = 8. These 2 parameters are used in --- movement calculation. Physics.MotionType doesn't really matter in this case and engine uses it only to --- setup these parameters from bp.Footprint ----@param Name string ----@param defaultVal any? -function GetStat(Name,defaultVal) - unit:GetStat("h1_SetSalemAmph", 1) -- 1 = on, 0 = off. -end \ No newline at end of file diff --git a/engine/Sim/Unit.lua b/engine/Sim/Unit.lua index ff305ea867..d1a761b09b 100644 --- a/engine/Sim/Unit.lua +++ b/engine/Sim/Unit.lua @@ -125,6 +125,11 @@ end function Unit:EnableManipulators(bone, Enable) end +---Forces game to use AltFootprint for the unit +---@param state boolean +function Unit:ForceAltFootPrint(state) +end + --- Returns the unit's multiplier to a damage type ---@param damageTypeName DamageType ---@return number @@ -269,7 +274,6 @@ end ---@param statName string ---@param defaultVal? number ---@return number --- Special case for the Salem: GetStat("h1_SetSalemAmph", 0 or 1) will Disable/Enable amphibious mode function Unit:GetStat(statName, defaultVal) end diff --git a/units/URS0201/URS0201_script.lua b/units/URS0201/URS0201_script.lua index ba4c8330ed..4e7d7886f3 100644 --- a/units/URS0201/URS0201_script.lua +++ b/units/URS0201/URS0201_script.lua @@ -173,7 +173,7 @@ URS0201 = ClassUnit(CSeaUnit) { CSeaUnit.OnScriptBitSet(self, bit) if bit == 1 then if self.Layer ~= 'Land' then - self:GetStat("h1_SetSalemAmph", 0) + self:ForceAltFootPrint(true) else self:SetScriptBit('RULEUTC_WeaponToggle', false) end @@ -183,7 +183,7 @@ URS0201 = ClassUnit(CSeaUnit) { OnScriptBitClear = function(self, bit) CSeaUnit.OnScriptBitClear(self, bit) if bit == 1 then - self:GetStat("h1_SetSalemAmph", 1) + self:ForceAltFootPrint(false) end end, } diff --git a/units/URS0201/URS0201_unit.bp b/units/URS0201/URS0201_unit.bp index 2b12d3c442..1eff446600 100644 --- a/units/URS0201/URS0201_unit.bp +++ b/units/URS0201/URS0201_unit.bp @@ -207,6 +207,13 @@ UnitBlueprint{ BuildCostMass = 2250, BuildTime = 10000, }, + AltFootprint = -- water only + { + SizeX = 2, + SizeZ = 8, + OccupancyCaps = 8, + MinWaterDepth = 1.5, + }, Footprint = { SizeX = 2, SizeZ = 8, From 5302a1b587fcb11e184e90da880322e988dc6b6e Mon Sep 17 00:00:00 2001 From: lL1l1 <82986251+lL1l1@users.noreply.github.com> Date: Thu, 7 Nov 2024 05:39:45 -0800 Subject: [PATCH 07/33] Fix inaccuracy of the `DoTTime` weapon stat (#6457) Fixes the DoT effect taking less time than the stat indicates, and updates blueprints to the lower actual time. --- changelog/snippets/fix.6457.md | 3 + lua/sim/DefaultDamage.lua | 87 ++++++++++++++-------- lua/sim/Projectile.lua | 131 +++++++++++++-------------------- units/DAA0206/DAA0206_unit.bp | 2 +- units/DEA0202/DEA0202_unit.bp | 2 +- units/UAB2302/UAB2302_unit.bp | 2 +- units/UAB2303/UAB2303_unit.bp | 2 +- units/UAL0304/UAL0304_unit.bp | 2 +- units/UEA0103/UEA0103_unit.bp | 2 +- units/URA0204/URA0204_unit.bp | 2 +- units/URB2109/URB2109_unit.bp | 2 +- units/URB2205/URB2205_unit.bp | 2 +- units/URS0201/URS0201_unit.bp | 2 +- units/URS0203/URS0203_unit.bp | 2 +- units/URS0302/URS0302_unit.bp | 2 +- units/URS0304/URS0304_unit.bp | 2 +- units/XRB2308/XRB2308_unit.bp | 2 +- units/XRB2309/XRB2309_unit.bp | 2 +- units/XRL0305/XRL0305_unit.bp | 2 +- units/XRL0403/XRL0403_unit.bp | 2 +- units/XRS0204/XRS0204_unit.bp | 2 +- 21 files changed, 128 insertions(+), 129 deletions(-) create mode 100644 changelog/snippets/fix.6457.md diff --git a/changelog/snippets/fix.6457.md b/changelog/snippets/fix.6457.md new file mode 100644 index 0000000000..012410946c --- /dev/null +++ b/changelog/snippets/fix.6457.md @@ -0,0 +1,3 @@ +- (#6457) Fix weapon projectiles dealing damage over time over a shorter duration than given by the `DoTTime` stat. + - `DoTTime` for FAF units is adjusted to the old actual DoT duration, so balance is not changed. + - Unit databases will now show the actual damage over time duration. diff --git a/lua/sim/DefaultDamage.lua b/lua/sim/DefaultDamage.lua index 017e78c645..24f438ae8e 100644 --- a/lua/sim/DefaultDamage.lua +++ b/lua/sim/DefaultDamage.lua @@ -14,66 +14,90 @@ local DamageArea = DamageArea -- cache for performance local VectorCache = Vector(0, 0, 0) -local MathFloor = math.floor -local CoroutineYield = coroutine.yield +local MathMod = math.mod +local MATH_IRound = MATH_IRound +local WaitTicks = WaitTicks local EntityBeenDestroyed = _G.moho.entity_methods.BeenDestroyed local EntityGetPositionXYZ = _G.moho.entity_methods.GetPositionXYZ ---- Performs damage over time on a unit. +--- Performs damage over time on a target, waiting the interval *before* dealing damage. ---@param instigator Unit ----@param unit Unit ----@param pulses any ----@param pulseTime integer +---@param target Unit | Prop | Projectile +---@param pulses number +---@param pulseInterval number ---@param damage number ----@param damType DamageType ----@param friendly boolean -function UnitDoTThread (instigator, unit, pulses, pulseTime, damage, damType, friendly) - +---@param damageType DamageType +function UnitDoTThread(instigator, target, pulses, pulseInterval, damage, damageType) -- localize for performance local position = VectorCache - local DamageArea = DamageArea - local CoroutineYield = CoroutineYield + local Damage = Damage + local EntityGetPositionXYZ = EntityGetPositionXYZ + local WaitTicks = WaitTicks + local MathMod = MathMod - -- convert time to ticks - pulseTime = 10 * pulseTime + 1 + -- convert seconds to ticks, have to "wait" 1 extra tick to get to the end of the current tick + pulseInterval = 10 * pulseInterval + 1 + -- accumulator to compensate for error caused by `WaitTicks` only working with integers + local accum = 0 for i = 1, pulses do - if unit and not EntityBeenDestroyed(unit) then - position[1], position[2], position[3] = EntityGetPositionXYZ(unit) - Damage(instigator, position, unit, damage, damType ) + if target and not EntityBeenDestroyed(target) then + position[1], position[2], position[3] = EntityGetPositionXYZ(target) + Damage(instigator, position, target, damage, damageType) else break end - CoroutineYield(pulseTime) + accum = accum + pulseInterval + if accum > 1 then + -- final accumulator value may be #.999 which needs to be rounded + if i == pulses then + WaitTicks(MATH_IRound(accum)) + else + WaitTicks(accum) + accum = MathMod(accum, 1) + end + end end end ---- Performs damage over time in a given area. +--- Performs damage over time in a given area, waiting the interval *before* dealing damage. ---@param instigator Unit ---@param position Vector ---@param pulses number ----@param pulseTime number +---@param pulseInterval number ---@param radius number ---@param damage number ----@param damType DamageType ----@param friendly boolean -function AreaDoTThread (instigator, position, pulses, pulseTime, radius, damage, damType, friendly) - +---@param damageType DamageType +---@param damageFriendly boolean +---@param damageSelf boolean +function AreaDoTThread(instigator, position, pulses, pulseInterval, radius, damage, damageType, damageFriendly, damageSelf) -- localize for performance local DamageArea = DamageArea - local CoroutineYield = CoroutineYield + local WaitTicks = WaitTicks + local MathMod = MathMod - -- compute ticks between pulses - pulseTime = 10 * pulseTime + 1 + -- convert seconds to ticks, have to "wait" 1 extra tick to get to the end of the current tick + pulseInterval = 10 * pulseInterval + 1 + -- accumulator to compensate for error caused by `WaitTicks` only working with integers + local accum = 0 for i = 1, pulses do - DamageArea(instigator, position, radius, damage, damType, friendly) - CoroutineYield(pulseTime) + accum = accum + pulseInterval + if accum > 1 then + -- final accumulator value may be #.999 which needs to be rounded + if i == pulses then + WaitTicks(MATH_IRound(accum)) + else + WaitTicks(accum) + accum = MathMod(accum, 1) + end + end + DamageArea(instigator, position, radius, damage, damageType, damageFriendly, damageSelf) end end --- Deprecated functionality -- +--#region Deprecated functionality -- SCALABLE RADIUS AREA DOT -- - Allows for a scalable damage radius that begins with DamageStartRadius and ends @@ -101,4 +125,5 @@ function ScalableRadiusAreaDoT(entity) end end entity:Destroy() -end \ No newline at end of file +end +--#endregion diff --git a/lua/sim/Projectile.lua b/lua/sim/Projectile.lua index bd5aa5519c..7f4b44fec1 100644 --- a/lua/sim/Projectile.lua +++ b/lua/sim/Projectile.lua @@ -612,16 +612,11 @@ Projectile = ClassProjectile(ProjectileMethods, DebugProjectileComponent) { end, --- Called by Lua to process the damage logic of a projectile - -- @param self The projectile itself - -- @param instigator The launcher, and if it doesn't exist, the projectile itself - -- @param DamageData The damage data passed by the weapon - -- @param targetEntity The entity we hit, is nil if we hit terrain - -- @param cachedPosition A cached position that is passed to prevent table allocations, can not be used in fork threads and / or after a yield statement ---@param self Projectile - ---@param instigator Unit + ---@param instigator Unit # The launcher, and if it doesn't exist, the projectile itself ---@param DamageData WeaponDamageTable # passed by the weapon - ---@param targetEntity Unit | Prop | nil - ---@param cachedPosition Vector + ---@param targetEntity Unit | Prop | nil # nil if hitting terrain + ---@param cachedPosition Vector # A cached position that is passed to prevent table allocations, can not be used in fork threads and / or after a yield statement DoDamage = function(self, instigator, DamageData, targetEntity, cachedPosition) -- this may be a cached vector, we can not send this to threads or use after waiting statements! @@ -637,66 +632,63 @@ Projectile = ClassProjectile(ProjectileMethods, DebugProjectileComponent) { local damageFriendly = DamageData.DamageFriendly local damageSelf = DamageData.DamageSelf or false - -- check for damage-over-time - local DoTTime = DamageData.DoTTime - if DoTTime <= 0 then - -- no damage over time, do radius-based damage + -- do initial damage in a radius + DamageArea( + instigator, + cachedPosition, + radius, + damage + (DamageData.InitialDamageAmount or 0), + damageType, + damageFriendly, + damageSelf + ) + + local damageToShields = DamageData.DamageToShields + if damageToShields then DamageArea( instigator, cachedPosition, radius, - damage, - damageType, + damageToShields, + "FAF_AntiShield", damageFriendly, damageSelf ) + end - local damageToShields = DamageData.DamageToShields - if damageToShields then - DamageArea( - instigator, - cachedPosition, - radius, - damageToShields, - "FAF_AntiShield", - damageFriendly, - damageSelf - ) - end - else - -- check for initial damage - local initialDmg = DamageData.InitialDamageAmount - if initialDmg > 0 then - DamageArea( + -- check for and deal damage over time + local DoTTime = DamageData.DoTTime + if DoTTime > 0 then + -- initial damage pulse was already dealt so subtract 1 + local DoTPulses = DamageData.DoTPulses - 1 + if DoTPulses >= 1 then + ForkThread( + AreaDoTThread, instigator, - cachedPosition, + self:GetPosition(), -- can't use cachedPosition here: breaks invariant + DoTPulses, + (DoTTime / (DoTPulses)), radius, - initialDmg, + damage, damageType, - damageFriendly, - damageSelf + damageFriendly ) end - - -- apply damage over time - local DoTPulses = DamageData.DoTPulses or 1 - ForkThread( - AreaDoTThread, - instigator, - self:GetPosition(), -- can't use cachedPosition here: breaks invariant - DoTPulses, - (DoTTime / (DoTPulses)), - radius, - damage, - damageType, - damageFriendly - ) end -- damage a single entity elseif targetEntity then local damageType = DamageData.DamageType + -- do initial damage + Damage( + instigator, + cachedPosition, + targetEntity, + damage + (DamageData.InitialDamageAmount or 0), + damageType + ) + local damageToShields = DamageData.DamageToShields if damageToShields then Damage( @@ -708,43 +700,22 @@ Projectile = ClassProjectile(ProjectileMethods, DebugProjectileComponent) { ) end - -- check for damage-over-time + -- check for and apply damage over time local DoTTime = DamageData.DoTTime - if DoTTime <= 0 then - - -- no damage over time, do single target damage - Damage( - instigator, - cachedPosition, - targetEntity, - damage, - damageType - ) - else - -- check for initial damage - local initialDmg = DamageData.InitialDamageAmount or 0 - if initialDmg > 0 then - Damage( + if DoTTime > 0 then + -- initial damage pulse was already dealt so subtract 1 + local DoTPulses = DamageData.DoTPulses - 1 + if DoTPulses >= 1 then + ForkThread( + UnitDoTThread, instigator, - cachedPosition, targetEntity, - initialDmg, + DoTPulses, + (DoTTime / (DoTPulses)), + damage, damageType ) end - - -- apply damage over time - local DoTPulses = DamageData.DoTPulses or 1 - ForkThread( - UnitDoTThread, - instigator, - targetEntity, - DoTPulses, - (DoTTime / (DoTPulses)), - damage, - damageType, - DamageData.DamageFriendly - ) end end end diff --git a/units/DAA0206/DAA0206_unit.bp b/units/DAA0206/DAA0206_unit.bp index 070f9a3113..abafc533da 100644 --- a/units/DAA0206/DAA0206_unit.bp +++ b/units/DAA0206/DAA0206_unit.bp @@ -170,7 +170,7 @@ UnitBlueprint{ DamageType = "Normal", DisplayName = "Kamikaze", DoTPulses = 20, - DoTTime = 10, + DoTTime = 9.5, EffectiveRadius = 0, FireTargetLayerCapsTable = { Air = "Land|Seabed|Water", diff --git a/units/DEA0202/DEA0202_unit.bp b/units/DEA0202/DEA0202_unit.bp index 4ba15db541..f1b400b75b 100644 --- a/units/DEA0202/DEA0202_unit.bp +++ b/units/DEA0202/DEA0202_unit.bp @@ -346,7 +346,7 @@ UnitBlueprint{ DamageType = "Normal", DisplayName = "Napalm Carpet Bomb", DoTPulses = 10, - DoTTime = 6, + DoTTime = 5.4, FireTargetLayerCapsTable = { Air = "Land|Seabed|Water", Land = "Land|Seabed|Water", diff --git a/units/UAB2302/UAB2302_unit.bp b/units/UAB2302/UAB2302_unit.bp index 9eee866882..ad117bd457 100644 --- a/units/UAB2302/UAB2302_unit.bp +++ b/units/UAB2302/UAB2302_unit.bp @@ -149,7 +149,7 @@ UnitBlueprint{ DamageType = "Normal", DisplayName = "Sonance Artillery", DoTPulses = 2, - DoTTime = 2, + DoTTime = 1, EnergyDrainPerSecond = 4250, EnergyRequired = 17000, FireTargetLayerCapsTable = { diff --git a/units/UAB2303/UAB2303_unit.bp b/units/UAB2303/UAB2303_unit.bp index f8387ad5cb..c887082ca9 100644 --- a/units/UAB2303/UAB2303_unit.bp +++ b/units/UAB2303/UAB2303_unit.bp @@ -132,7 +132,7 @@ UnitBlueprint{ DamageType = "Normal", DisplayName = "Miasma Artillery", DoTPulses = 5, - DoTTime = 1, + DoTTime = 0.8, EnergyDrainPerSecond = 145, EnergyRequired = 1450, FireTargetLayerCapsTable = { diff --git a/units/UAL0304/UAL0304_unit.bp b/units/UAL0304/UAL0304_unit.bp index 23061e9ad2..e3c3fb8ec8 100644 --- a/units/UAL0304/UAL0304_unit.bp +++ b/units/UAL0304/UAL0304_unit.bp @@ -148,7 +148,7 @@ UnitBlueprint{ DamageType = "Normal", DisplayName = "Sonance Artillery", DoTPulses = 15, - DoTTime = 5, + DoTTime = 4.2, FireTargetLayerCapsTable = { Land = "Land|Water|Seabed", Water = "Land|Water|Seabed", diff --git a/units/UEA0103/UEA0103_unit.bp b/units/UEA0103/UEA0103_unit.bp index 5825b6364c..d4ec3a20b9 100644 --- a/units/UEA0103/UEA0103_unit.bp +++ b/units/UEA0103/UEA0103_unit.bp @@ -192,7 +192,7 @@ UnitBlueprint{ DamageType = "Normal", DisplayName = "Napalm Carpet Bomb", DoTPulses = 10, - DoTTime = 4.2, + DoTTime = 3.6, FireTargetLayerCapsTable = { Air = "Land|Water|Seabed", Land = "Land|Water|Seabed", diff --git a/units/URA0204/URA0204_unit.bp b/units/URA0204/URA0204_unit.bp index 0275e3382b..1c1ad5f1f3 100644 --- a/units/URA0204/URA0204_unit.bp +++ b/units/URA0204/URA0204_unit.bp @@ -205,7 +205,7 @@ UnitBlueprint{ DamageType = "Normal", DisplayName = "Nanite Torpedo", DoTPulses = 5, - DoTTime = 1, + DoTTime = 0.8, FireTargetLayerCapsTable = { Air = "Seabed|Sub|Water", Land = "Seabed|Sub|Water", diff --git a/units/URB2109/URB2109_unit.bp b/units/URB2109/URB2109_unit.bp index c4a5d6627d..6712a6e0dc 100644 --- a/units/URB2109/URB2109_unit.bp +++ b/units/URB2109/URB2109_unit.bp @@ -132,7 +132,7 @@ UnitBlueprint{ DamageType = "Normal", DisplayName = "Nanite Torpedo", DoTPulses = 5, - DoTTime = 1, + DoTTime = 0.8, FireTargetLayerCapsTable = { Water = "Seabed|Sub|Water" }, FiringTolerance = 30, Label = "Turret01", diff --git a/units/URB2205/URB2205_unit.bp b/units/URB2205/URB2205_unit.bp index 38ba378189..5e7c815e61 100644 --- a/units/URB2205/URB2205_unit.bp +++ b/units/URB2205/URB2205_unit.bp @@ -152,7 +152,7 @@ UnitBlueprint{ DamageType = "Normal", DisplayName = "Nanite Torpedo", DoTPulses = 5, - DoTTime = 0.5, + DoTTime = 0.4, FireTargetLayerCapsTable = { Water = "Seabed|Sub|Water" }, FiringTolerance = 60, Label = "Turret01", diff --git a/units/URS0201/URS0201_unit.bp b/units/URS0201/URS0201_unit.bp index 1eff446600..e91bfa3eda 100644 --- a/units/URS0201/URS0201_unit.bp +++ b/units/URS0201/URS0201_unit.bp @@ -420,7 +420,7 @@ UnitBlueprint{ DamageType = "Normal", DisplayName = "Nanite Torpedo", DoTPulses = 5, - DoTTime = 0.5, + DoTTime = 0.4, FireTargetLayerCapsTable = { Water = "Seabed|Sub|Water" }, FiringTolerance = 2, Label = "TorpedoL", diff --git a/units/URS0203/URS0203_unit.bp b/units/URS0203/URS0203_unit.bp index ddeabb2842..d399a97fd2 100644 --- a/units/URS0203/URS0203_unit.bp +++ b/units/URS0203/URS0203_unit.bp @@ -217,7 +217,7 @@ UnitBlueprint{ DamageType = "Normal", DisplayName = "Nanite Torpedo", DoTPulses = 5, - DoTTime = 1, + DoTTime = 0.8, EffectiveRadius = 29, FireTargetLayerCapsTable = { Sub = "Seabed|Sub|Water", diff --git a/units/URS0302/URS0302_unit.bp b/units/URS0302/URS0302_unit.bp index df88beecfc..48879ad053 100644 --- a/units/URS0302/URS0302_unit.bp +++ b/units/URS0302/URS0302_unit.bp @@ -465,7 +465,7 @@ UnitBlueprint{ DamageType = "Normal", DisplayName = "Nanite Torpedo", DoTPulses = 5, - DoTTime = 0.5, + DoTTime = 0.4, FireTargetLayerCapsTable = { Water = "Seabed|Sub|Water" }, FiringTolerance = 2, Label = "Torpedo01", diff --git a/units/URS0304/URS0304_unit.bp b/units/URS0304/URS0304_unit.bp index 1a424b2ce2..3d3eae8b26 100644 --- a/units/URS0304/URS0304_unit.bp +++ b/units/URS0304/URS0304_unit.bp @@ -330,7 +330,7 @@ UnitBlueprint{ DamageType = "Normal", DisplayName = "Nanite Torpedo", DoTPulses = 5, - DoTTime = 0.5, + DoTTime = 0.4, FireTargetLayerCapsTable = { Sub = "Seabed|Sub|Water", Water = "Seabed|Sub|Water", diff --git a/units/XRB2308/XRB2308_unit.bp b/units/XRB2308/XRB2308_unit.bp index c372f13d63..c525f4fbd6 100644 --- a/units/XRB2308/XRB2308_unit.bp +++ b/units/XRB2308/XRB2308_unit.bp @@ -148,7 +148,7 @@ UnitBlueprint{ DamageType = "Normal", DisplayName = "Kril Torpedo", DoTPulses = 5, - DoTTime = 0.5, + DoTTime = 0.4, FireTargetLayerCapsTable = { Water = "Seabed|Sub|Water" }, FiringTolerance = 2, Label = "Turret01", diff --git a/units/XRB2309/XRB2309_unit.bp b/units/XRB2309/XRB2309_unit.bp index 692f7fb0a4..8fa055d0c8 100644 --- a/units/XRB2309/XRB2309_unit.bp +++ b/units/XRB2309/XRB2309_unit.bp @@ -153,7 +153,7 @@ UnitBlueprint{ DamageType = "Normal", DisplayName = "Kril Torpedo", DoTPulses = 5, - DoTTime = 0.5, + DoTTime = 0.4, FireTargetLayerCapsTable = { Sub = "Seabed|Sub|Water" }, FiringTolerance = 2, Label = "Turret01", diff --git a/units/XRL0305/XRL0305_unit.bp b/units/XRL0305/XRL0305_unit.bp index 5dca16f87c..f1b8c13b75 100644 --- a/units/XRL0305/XRL0305_unit.bp +++ b/units/XRL0305/XRL0305_unit.bp @@ -232,7 +232,7 @@ UnitBlueprint{ DamageType = "Normal", DisplayName = "Nanite Torpedo Launcher", DoTPulses = 5, - DoTTime = 0.5, + DoTTime = 0.4, FireTargetLayerCapsTable = { Seabed = "Seabed|Sub|Water" }, FiringTolerance = 2, Label = "Torpedo", diff --git a/units/XRL0403/XRL0403_unit.bp b/units/XRL0403/XRL0403_unit.bp index 8b27cc961e..602f500f8f 100644 --- a/units/XRL0403/XRL0403_unit.bp +++ b/units/XRL0403/XRL0403_unit.bp @@ -406,7 +406,7 @@ UnitBlueprint{ DamageType = "Normal", DisplayName = "Nanite Torpedo Launcher", DoTPulses = 5, - DoTTime = 0.5, + DoTTime = 0.4, FireTargetLayerCapsTable = { Seabed = "Seabed|Sub|Water" }, FiringTolerance = 2, Label = "Torpedo01", diff --git a/units/XRS0204/XRS0204_unit.bp b/units/XRS0204/XRS0204_unit.bp index e1508dd5f4..a226dad8d7 100644 --- a/units/XRS0204/XRS0204_unit.bp +++ b/units/XRS0204/XRS0204_unit.bp @@ -220,7 +220,7 @@ UnitBlueprint{ DamageType = "Normal", DisplayName = "Nanite Torpedo", DoTPulses = 5, - DoTTime = 0.5, + DoTTime = 0.4, FireTargetLayerCapsTable = { Sub = "Seabed|Sub|Water", Water = "Seabed|Sub|Water", From 3cc1ec67fff46ac3405883797a655ef6c4c73bd6 Mon Sep 17 00:00:00 2001 From: maudlin27 <95254039+maudlin27@users.noreply.github.com> Date: Thu, 7 Nov 2024 15:54:07 +0000 Subject: [PATCH 08/33] Fix campaign objectives not being completable by AI players (#6369) --- changelog/snippets/fix.6369.md | 1 + lua/SimObjectives.lua | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 changelog/snippets/fix.6369.md diff --git a/changelog/snippets/fix.6369.md b/changelog/snippets/fix.6369.md new file mode 100644 index 0000000000..44afd469ec --- /dev/null +++ b/changelog/snippets/fix.6369.md @@ -0,0 +1 @@ +- (#6369) AI player teammates should now be able to complete campaign objectives such as the opening Supcom mission's objective to build 3 mexes. diff --git a/lua/SimObjectives.lua b/lua/SimObjectives.lua index 39149d64c0..ca67602cd2 100644 --- a/lua/SimObjectives.lua +++ b/lua/SimObjectives.lua @@ -1117,7 +1117,8 @@ function MakeListFromTarget(Target) for _, armyName in Target.Armies do if armyName == "HumanPlayers" then for iArmy, strArmy in pairs(tblArmy) do - if ScenarioInfo.ArmySetup[strArmy].Human then + --In campaign, if an AI mod is being used to provide an AI 'player', then this should also be included otherwise it can render some missions impossible to complete + if ScenarioInfo.ArmySetup[strArmy].Human or (ScenarioInfo.type == 'campaign_coop' and string.sub(strArmy, 1, 6) == 'Player') then resultList[GetArmyBrain(iArmy)] = true end end From 5ae907e3dac7516278c3ff8824bc5ac9e1823c04 Mon Sep 17 00:00:00 2001 From: Alexander <84857900+4z0t@users.noreply.github.com> Date: Fri, 8 Nov 2024 00:04:32 +0300 Subject: [PATCH 09/33] Fix typo in an engine function (#6521) --- engine/Sim/Unit.lua | 2 +- units/URS0201/URS0201_script.lua | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/engine/Sim/Unit.lua b/engine/Sim/Unit.lua index d1a761b09b..ea46780f7e 100644 --- a/engine/Sim/Unit.lua +++ b/engine/Sim/Unit.lua @@ -127,7 +127,7 @@ end ---Forces game to use AltFootprint for the unit ---@param state boolean -function Unit:ForceAltFootPrint(state) +function Unit:ForceAltFootprint(state) end --- Returns the unit's multiplier to a damage type diff --git a/units/URS0201/URS0201_script.lua b/units/URS0201/URS0201_script.lua index 4e7d7886f3..924a28955e 100644 --- a/units/URS0201/URS0201_script.lua +++ b/units/URS0201/URS0201_script.lua @@ -173,7 +173,7 @@ URS0201 = ClassUnit(CSeaUnit) { CSeaUnit.OnScriptBitSet(self, bit) if bit == 1 then if self.Layer ~= 'Land' then - self:ForceAltFootPrint(true) + self:ForceAltFootprint(true) else self:SetScriptBit('RULEUTC_WeaponToggle', false) end @@ -183,7 +183,7 @@ URS0201 = ClassUnit(CSeaUnit) { OnScriptBitClear = function(self, bit) CSeaUnit.OnScriptBitClear(self, bit) if bit == 1 then - self:ForceAltFootPrint(false) + self:ForceAltFootprint(false) end end, } From d1d28f7e1449a8b8d8fa3efc3a467c2e52b9331e Mon Sep 17 00:00:00 2001 From: Basilisk3 <126026384+Basilisk3@users.noreply.github.com> Date: Sun, 10 Nov 2024 07:39:44 +0100 Subject: [PATCH 10/33] Fix selection boxes of air units intersecting with the terrain (#6504) --- changelog/snippets/fix.6504.md | 1 + units/UAA0104/UAA0104_unit.bp | 1 + units/UEA0104/UEA0104_unit.bp | 1 + units/UEA0107/UEA0107_unit.bp | 1 + units/UEA0203/UEA0203_unit.bp | 1 + units/URA0104/URA0104_unit.bp | 1 + units/URA0107/URA0107_unit.bp | 1 + units/XEA0306/XEA0306_unit.bp | 1 + units/XSA0104/XSA0104_unit.bp | 1 + units/XSA0107/XSA0107_unit.bp | 1 + units/XSA0203/XSA0203_unit.bp | 4 ++-- 11 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 changelog/snippets/fix.6504.md diff --git a/changelog/snippets/fix.6504.md b/changelog/snippets/fix.6504.md new file mode 100644 index 0000000000..11ab497c4b --- /dev/null +++ b/changelog/snippets/fix.6504.md @@ -0,0 +1 @@ +- (#6504) Improve the selection boxes of various air units. Alleviate the issue of terrain partially obstructing the selection boxes of most transports when they are grounded. Slightly improve the visuals of the Vulthoo's (Seraphim T2 Gunship's) selection box. diff --git a/units/UAA0104/UAA0104_unit.bp b/units/UAA0104/UAA0104_unit.bp index 41d8df36fe..a26cfa9a75 100644 --- a/units/UAA0104/UAA0104_unit.bp +++ b/units/UAA0104/UAA0104_unit.bp @@ -150,6 +150,7 @@ UnitBlueprint{ MeshExtentsZ = 2, MotionType = "RULEUMT_Air", }, + SelectionCenterOffsetY = 0.15, SelectionSizeX = 2.75, SelectionSizeZ = 3.8, SelectionThickness = 0.26, diff --git a/units/UEA0104/UEA0104_unit.bp b/units/UEA0104/UEA0104_unit.bp index 3ac3ec49e2..76cda6e8bb 100644 --- a/units/UEA0104/UEA0104_unit.bp +++ b/units/UEA0104/UEA0104_unit.bp @@ -227,6 +227,7 @@ UnitBlueprint{ MeshExtentsZ = 8.5, MotionType = "RULEUMT_Air", }, + SelectionCenterOffsetY = 0.15, SelectionSizeX = 0.95, SelectionSizeZ = 5, SelectionThickness = 0.19, diff --git a/units/UEA0107/UEA0107_unit.bp b/units/UEA0107/UEA0107_unit.bp index 522466dffd..d688587b94 100644 --- a/units/UEA0107/UEA0107_unit.bp +++ b/units/UEA0107/UEA0107_unit.bp @@ -215,6 +215,7 @@ UnitBlueprint{ MeshExtentsZ = 5.25, MotionType = "RULEUMT_Air", }, + SelectionCenterOffsetY = 0.15, SelectionSizeX = 0.8, SelectionSizeZ = 3, SelectionThickness = 0.22, diff --git a/units/UEA0203/UEA0203_unit.bp b/units/UEA0203/UEA0203_unit.bp index 5ad15acc05..80cefa52ed 100644 --- a/units/UEA0203/UEA0203_unit.bp +++ b/units/UEA0203/UEA0203_unit.bp @@ -209,6 +209,7 @@ UnitBlueprint{ MeshExtentsZ = 3, MotionType = "RULEUMT_Air", }, + SelectionCenterOffsetY = 0.15, SelectionSizeX = 0.6, SelectionSizeZ = 0.9, SelectionThickness = 0.49, diff --git a/units/URA0104/URA0104_unit.bp b/units/URA0104/URA0104_unit.bp index 097877e7ec..bb35776eaa 100644 --- a/units/URA0104/URA0104_unit.bp +++ b/units/URA0104/URA0104_unit.bp @@ -165,6 +165,7 @@ UnitBlueprint{ MaxSpeed = 0.5, MotionType = "RULEUMT_Air", }, + SelectionCenterOffsetY = 0.15, SelectionSizeX = 1.65, SelectionSizeZ = 4.25, SelectionThickness = 0.22, diff --git a/units/URA0107/URA0107_unit.bp b/units/URA0107/URA0107_unit.bp index b3e520ccf2..26779135f6 100644 --- a/units/URA0107/URA0107_unit.bp +++ b/units/URA0107/URA0107_unit.bp @@ -152,6 +152,7 @@ UnitBlueprint{ MaxSpeed = 0.5, MotionType = "RULEUMT_Air", }, + SelectionCenterOffsetY = 0.15, SelectionSizeX = 1.6, SelectionSizeZ = 3.25, SelectionThickness = 0.21, diff --git a/units/XEA0306/XEA0306_unit.bp b/units/XEA0306/XEA0306_unit.bp index 67072dcd97..15b586da9d 100644 --- a/units/XEA0306/XEA0306_unit.bp +++ b/units/XEA0306/XEA0306_unit.bp @@ -257,6 +257,7 @@ UnitBlueprint{ MeshExtentsZ = 8.5, MotionType = "RULEUMT_Air", }, + SelectionCenterOffsetY = 0.2, SelectionSizeX = 2.7, SelectionSizeZ = 4.8, SelectionThickness = 0.2, diff --git a/units/XSA0104/XSA0104_unit.bp b/units/XSA0104/XSA0104_unit.bp index 51a8e3d696..5958c39bae 100644 --- a/units/XSA0104/XSA0104_unit.bp +++ b/units/XSA0104/XSA0104_unit.bp @@ -170,6 +170,7 @@ UnitBlueprint{ MeshExtentsZ = 6, MotionType = "RULEUMT_Air", }, + SelectionCenterOffsetY = 0.15, SelectionSizeX = 2.1, SelectionSizeZ = 4.5, SelectionThickness = 0.23, diff --git a/units/XSA0107/XSA0107_unit.bp b/units/XSA0107/XSA0107_unit.bp index 20e75538c8..4e985a5250 100644 --- a/units/XSA0107/XSA0107_unit.bp +++ b/units/XSA0107/XSA0107_unit.bp @@ -157,6 +157,7 @@ UnitBlueprint{ MeshExtentsZ = 1.6, MotionType = "RULEUMT_Air", }, + SelectionCenterOffsetY = 0.15, SelectionSizeX = 2.1, SelectionSizeZ = 4.5, SelectionThickness = 0.23, diff --git a/units/XSA0203/XSA0203_unit.bp b/units/XSA0203/XSA0203_unit.bp index a9e1f97ceb..82094dc327 100644 --- a/units/XSA0203/XSA0203_unit.bp +++ b/units/XSA0203/XSA0203_unit.bp @@ -143,8 +143,8 @@ UnitBlueprint{ MotionType = "RULEUMT_Air", }, SelectionSizeX = 0.5, - SelectionSizeZ = 1.2, - SelectionThickness = 0.47, + SelectionSizeZ = 1.3, + SelectionThickness = 0.37, SizeSphere = 1.76, SizeX = 0.8, SizeY = 0.4, From 128f7d729f94cf8930160ceb6b952da7f76933a6 Mon Sep 17 00:00:00 2001 From: lL1l1 <82986251+lL1l1@users.noreply.github.com> Date: Sun, 10 Nov 2024 02:48:15 -0800 Subject: [PATCH 11/33] Add a flat threshold for the brain energy manager (#6514) Places an upper limit on the energy requirement to turn energy-consuming units back on after an energy stall --- changelog/snippets/fix.6514.md | 1 + engine/Sim/CAiBrain.lua | 32 ++++++++++----- lua/TriggerManager.lua | 14 +++++++ .../EnergyManagerBrainComponent.lua | 39 +++++++++++++++---- 4 files changed, 68 insertions(+), 18 deletions(-) create mode 100644 changelog/snippets/fix.6514.md diff --git a/changelog/snippets/fix.6514.md b/changelog/snippets/fix.6514.md new file mode 100644 index 0000000000..997f8b9578 --- /dev/null +++ b/changelog/snippets/fix.6514.md @@ -0,0 +1 @@ +- (#6514) Fix extremely large amounts of energy storage preventing structures from re-enabling themselves after an energy stall due to requiring 0.1% of storage to be full. Now there is an upper limit of 10k on the energy requirement, which is reached at 100k storage. diff --git a/engine/Sim/CAiBrain.lua b/engine/Sim/CAiBrain.lua index a0483eb4c3..b6c3fbdb75 100644 --- a/engine/Sim/CAiBrain.lua +++ b/engine/Sim/CAiBrain.lua @@ -202,7 +202,9 @@ end --- | 'Economy_Output_Energy' --- | 'Economy_Stored_Energy' --- | 'Economy_Reclaimed_Energy' +--- | 'Economy_Ratio_Energy' --- | 'Economy_MaxStorage_Energy' +--- | 'Economy_Trend_Energy' --- | 'Economy_PeakStorage_Energy' --- | 'Economy_TotalProduced_Mass' --- | 'Economy_TotalConsumed_Mass' @@ -210,7 +212,9 @@ end --- | 'Economy_Output_Mass' --- | 'Economy_Stored_Mass' --- | 'Economy_Reclaimed_Mass' +--- | 'Economy_Ratio_Mass' --- | 'Economy_MaxStorage_Mass' +--- | 'Economy_Trend_Mass' --- | 'Economy_PeakStorage_Mass' --- Returns the statistic of the army, if it doesn't exist it creates it and returns the default value @@ -484,23 +488,31 @@ function CAiBrain:PlatoonExists(platoon) end --- Remove an army stats trigger. --- @param statName String, army's stat, example: "Economy_Ratio_Mass". --- @param triggerName String, unique name of the trigger. +---@param statName AIBrainBlueprintStatUnits | AIBrainBlueprintStatEnemies | AIBrainBlueprintStatEconomy | AIBrainBlueprintStatDamage +---@param triggerName string # unique name of the trigger. function CAiBrain:RemoveArmyStatsTrigger(statName, triggerName) end --- Sets army's stat to value. --- @param statName String, army's stat, example: "Economy_Ratio_Mass". --- @param value Number. +---@param statName AIBrainBlueprintStatUnits | AIBrainBlueprintStatEnemies | AIBrainBlueprintStatEconomy | AIBrainBlueprintStatDamage # army's stat, example: "Economy_Ratio_Mass". +---@param value number function CAiBrain:SetArmyStat(statName, value) end +---@alias ComparatorString +---| "LessThan" +---| "LessThanOrEqual" +---| "GreaterThan" +---| "GreaterThanOrEqual" +---| "Equal" + --- Creates a new stat trigger. --- @param statName String, army's stat, example: "Economy_Ratio_Mass". --- @param triggerName String, unique name of the trigger. --- @param compareType String, available types: 'LessThan', 'LessThanOrEqual', 'GreaterThan', 'GreaterThanOrEqual', 'Equal'. --- @param value Number. -function CAiBrain:SetArmyStatsTrigger(statName, triggerName, compareType, value) +---@param statName AIBrainBlueprintStatUnits | AIBrainBlueprintStatEnemies | AIBrainBlueprintStatEconomy | AIBrainBlueprintStatDamage # army's stat +---@param triggerName string # unique name of the trigger. See `RemoveArmyStatsTrigger` to remove occupied names. +---@param compareType ComparatorString # available types: `LessThan`, `LessThanOrEqual`, `GreaterThan`, `GreaterThanOrEqual`, `Equal` +---@param value number # +---@param category EntityCategory? # +function CAiBrain:SetArmyStatsTrigger(statName, triggerName, compareType, value, category) end --- Set the current enemy for this brain to attack. @@ -518,7 +530,7 @@ function CAiBrain:SetGreaterOf(statname, val) end --- Set if the brain should share resources to the allies. --- @param bool ture/false +---@param bool boolean function CAiBrain:SetResourceSharing(bool) end diff --git a/lua/TriggerManager.lua b/lua/TriggerManager.lua index 0ebb533a97..30eaa989f8 100644 --- a/lua/TriggerManager.lua +++ b/lua/TriggerManager.lua @@ -394,6 +394,20 @@ Manager = { return true end, + ---@class EconStatsTriggerSpec + ---@field Name string + ---@field Parameters EconStatsTriggerParams + + ---@class EconStatsTriggerParams + ---@field Brain AIBrain + ---@field ResourceType 'Mass' | 'Energy' + ---@field EconType 'TotalProduced' | 'TotalConsumed' | 'Income' | 'Output' | 'Stored' | 'Reclaimed' | 'Ratio' | 'MaxStorage' | 'PeakStorage' | 'Trend' + ---@field CompareType ComparatorString? + ---@field Number number # Value to compare against + + ---@param self TriggerManager + ---@param spec EconStatsTriggerSpec + ---@return boolean EconStats = function(self, spec) local params = spec.Parameters if not params.ResourceType then diff --git a/lua/aibrains/components/EnergyManagerBrainComponent.lua b/lua/aibrains/components/EnergyManagerBrainComponent.lua index bb0bc96084..183f287eb6 100644 --- a/lua/aibrains/components/EnergyManagerBrainComponent.lua +++ b/lua/aibrains/components/EnergyManagerBrainComponent.lua @@ -241,7 +241,7 @@ EnergyManagerBrainComponent = ClassSimple { end, OnStatsTrigger = function(self, triggerName) - if triggerName == "EnergyDepleted" or triggerName == "EnergyViable" then + if triggerName == "EnergyDepleted" or triggerName == "EnergyViable" or triggerName == "SwitchEnergyViableTrigger" then self:OnEnergyTrigger(triggerName) end end, @@ -250,18 +250,41 @@ EnergyManagerBrainComponent = ClassSimple { ---@param self AIBrain ---@param triggerName string OnEnergyTrigger = function(self, triggerName) - if triggerName == "EnergyDepleted" then + if triggerName == "EnergyDepleted" or triggerName == "SwitchEnergyViableTrigger" then -- add trigger when we can recover units - self:SetArmyStatsTrigger('Economy_Ratio_Energy', 'EnergyViable', 'GreaterThanOrEqual', 0.1) - self.EnergyDepleted = true + local energyStorageThreshold = 100000 + local storageRatio = 0.1 + + if self:GetArmyStat('Economy_MaxStorage_Energy', 0).Value < energyStorageThreshold then + -- When we have little storage, turn back on above the ratio, or switch trigger after building enough storage. + self:RemoveArmyStatsTrigger("Economy_Stored_Energy", "EnergyViable") + self:SetArmyStatsTrigger('Economy_Ratio_Energy', 'EnergyViable', 'GreaterThanOrEqual', storageRatio) + + self:RemoveArmyStatsTrigger("Economy_Stored_Energy", "SwitchEnergyViableTrigger") + self:SetArmyStatsTrigger("Economy_MaxStorage_Energy", "SwitchEnergyViableTrigger", "GreaterThanOrEqual", energyStorageThreshold) + else + -- When we have a lot of storage, turn back on above the ratio of the threshold, or switch trigger after losing enough storage. + self:RemoveArmyStatsTrigger("Economy_Ratio_Energy", "EnergyViable") + self:SetArmyStatsTrigger("Economy_Stored_Energy", "EnergyViable", "GreaterThanOrEqual", energyStorageThreshold * storageRatio) + + self:RemoveArmyStatsTrigger("Economy_MaxStorage_Energy", "SwitchEnergyViableTrigger") + self:SetArmyStatsTrigger("Economy_MaxStorage_Energy", "SwitchEnergyViableTrigger", "LessThan", energyStorageThreshold) + end - -- recurse over the list of units and do callbacks accordingly - for id, entity in self.EnergyDependingUnits do - if not IsDestroyed(entity) then - entity:OnEnergyDepleted() + if triggerName == "EnergyDepleted" then + self.EnergyDepleted = true + + -- recurse over the list of units and do callbacks accordingly + for id, entity in self.EnergyDependingUnits do + if not IsDestroyed(entity) then + entity:OnEnergyDepleted() + end end end else + -- clean up the trigger switcher + self:RemoveArmyStatsTrigger("Economy_MaxStorage_Energy", "SwitchEnergyViableTrigger") + -- add trigger when we're depleted self:SetArmyStatsTrigger('Economy_Ratio_Energy', 'EnergyDepleted', 'LessThanOrEqual', 0.0) self.EnergyDepleted = false From 13511e28cd3cc00666252ee4a8ae487807202215 Mon Sep 17 00:00:00 2001 From: lL1l1 <82986251+lL1l1@users.noreply.github.com> Date: Sun, 10 Nov 2024 08:06:13 -0800 Subject: [PATCH 12/33] Fix runtime error when computing the dot product via `utilities.lua` (#6527) --- changelog/snippets/other.6438.md | 2 +- lua/utilities.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/changelog/snippets/other.6438.md b/changelog/snippets/other.6438.md index 711be31cd4..983cdb4dc4 100644 --- a/changelog/snippets/other.6438.md +++ b/changelog/snippets/other.6438.md @@ -1,4 +1,4 @@ -- (#5061, #6438) Add metamethods and utility functions for Vectors and Quaternions to simplify and clean up the code involving operations with them. +- (#5061, #6438, #6527) Add metamethods and utility functions for Vectors and Quaternions to simplify and clean up the code involving operations with them. - This **removes** the file `/lua/shared/quaternions.lua`, which was added in #4768 (Mar 4, 2023), so mods that use that file will have to be updated. - The metamethods (defined globally in `/lua/system/utils.lua`) include: - Vector/Vector2 addition/subtraction/negation diff --git a/lua/utilities.lua b/lua/utilities.lua index 43f5eecd53..4423c723ed 100644 --- a/lua/utilities.lua +++ b/lua/utilities.lua @@ -391,7 +391,7 @@ end ---@param v2 Vector ---@return number function DotP(v1, v2) - return v1[1] * v2[1] + v1[2] * v[2] + v[3] * v[3] + return v1[1] * v2[1] + v1[2] * v2[2] + v1[3] * v2[3] end --- Returns the conjugate of a quaternion From f0abd323f36fa9a4700d3a3acd48eb4d9d740bdb Mon Sep 17 00:00:00 2001 From: "(Jip) Willem Wijnia" Date: Mon, 11 Nov 2024 10:56:12 +0100 Subject: [PATCH 13/33] Bump the default depth for recursive printing to 3 (#6526) --- changelog/snippets/other.6526.md | 3 +++ lua/system/repr.lua | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 changelog/snippets/other.6526.md diff --git a/changelog/snippets/other.6526.md b/changelog/snippets/other.6526.md new file mode 100644 index 0000000000..201bd77d44 --- /dev/null +++ b/changelog/snippets/other.6526.md @@ -0,0 +1,3 @@ +- (#6526) Default the depth of recursive printing/logging to 3 tables deep + +The new default applies to all the `repr`-like functions. This only changes the developers experience. diff --git a/lua/system/repr.lua b/lua/system/repr.lua index b774b56711..a9c7a91216 100644 --- a/lua/system/repr.lua +++ b/lua/system/repr.lua @@ -260,7 +260,7 @@ end local function inspect(root, options) options = options or {} - local depth = options.depth or 1 + local depth = options.depth or 3 local newline = options.newline or '\n' local indent = options.indent or ' ' local meta = options.meta or false @@ -286,6 +286,10 @@ end repr = inspect repru = inspect reprs = inspect + +---@param root any +---@param options? DebugInspectOptions +---@return string reprsl = function(root, options) local str = inspect(root, options) LOG(str) From c6ca674d5428bd6989fa3d4a85ff168891944c9a Mon Sep 17 00:00:00 2001 From: "(Jip) Willem Wijnia" Date: Tue, 12 Nov 2024 16:52:52 +0100 Subject: [PATCH 14/33] Add (proper) key names for side mouse buttons (#6533) --- changelog/snippets/features.6533.md | 5 +++++ lua/keymap/keyNames.lua | 2 ++ lua/keymap/properKeyNames.lua | 2 ++ 3 files changed, 9 insertions(+) create mode 100644 changelog/snippets/features.6533.md diff --git a/changelog/snippets/features.6533.md b/changelog/snippets/features.6533.md new file mode 100644 index 0000000000..dd6ba54653 --- /dev/null +++ b/changelog/snippets/features.6533.md @@ -0,0 +1,5 @@ +- (#6533) Add support to assign hotkeys to secondary mouse buttons + +Their technical names are 'X Button 1' and 'X Button 2'. These are the buttons that are commonly found on the left or right side of a mouse. + +With thanks to RTD for the assembly patch. diff --git a/lua/keymap/keyNames.lua b/lua/keymap/keyNames.lua index f4d0c9c4b9..cd02f17c84 100644 --- a/lua/keymap/keyNames.lua +++ b/lua/keymap/keyNames.lua @@ -6,6 +6,8 @@ keyNames = { ['02'] = 'RightMouse', ['03'] = 'Cancel', ['04'] = 'MiddleMouse', + ['05'] = 'XButton1', + ['06'] = 'XButton2', ['08'] = 'Backspace', ['09'] = 'Tab', ['0C'] = 'Clear', diff --git a/lua/keymap/properKeyNames.lua b/lua/keymap/properKeyNames.lua index d2dbe3ce6e..bc2223ea12 100644 --- a/lua/keymap/properKeyNames.lua +++ b/lua/keymap/properKeyNames.lua @@ -169,4 +169,6 @@ properKeyNames = { ['OemClear'] = 'OemClear', ['Chevron'] = 'Chevron', ['Backtick'] = 'Backtick', + ['XButton1'] = 'XButton1', + ['XButton2'] = 'XButton2', } From c3485d0d8b778fa2b845907c1f2f52135d5f941f Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 12 Nov 2024 19:50:16 +0000 Subject: [PATCH 15/33] Apply new convention of processing enhancements to Cybran ACU (#6503) --- changelog/snippets/other.6498.md | 2 +- units/URL0001/URL0001_script.lua | 646 ++++++++++++++++++------------- 2 files changed, 384 insertions(+), 264 deletions(-) diff --git a/changelog/snippets/other.6498.md b/changelog/snippets/other.6498.md index 237a2857b1..ff37651b8c 100644 --- a/changelog/snippets/other.6498.md +++ b/changelog/snippets/other.6498.md @@ -1 +1 @@ -- (#6498, #6502) Refactor the Enhancements section in the ACU/SACU scripts to replace the long if/else chain with a more modular design that is easier to maintain and hook. Each enhancement has its own dedicated function, named with the format `ProcessEnhancement[EnhancementName]`. The CreateEnhancement function now calls the appropriate enhancement function automatically by that name format. +- (#6498, #6502, #6503) Refactor the Enhancements section in the ACU/SACU scripts to replace the long if/else chain with a more modular design that is easier to maintain and hook. Each enhancement has its own dedicated function, named with the format `ProcessEnhancement[EnhancementName]`. The CreateEnhancement function now calls the appropriate enhancement function automatically by that name format. diff --git a/units/URL0001/URL0001_script.lua b/units/URL0001/URL0001_script.lua index a84201fff8..e07206f936 100644 --- a/units/URL0001/URL0001_script.lua +++ b/units/URL0001/URL0001_script.lua @@ -29,21 +29,29 @@ local ACUDeathWeapon = import("/lua/sim/defaultweapons.lua").ACUDeathWeapon local CDFHeavyMicrowaveLaserGeneratorCom = CWeapons.CDFHeavyMicrowaveLaserGeneratorCom local CDFOverchargeWeapon = CWeapons.CDFOverchargeWeapon local CANTorpedoLauncherWeapon = CWeapons.CANTorpedoLauncherWeapon -local Entity = import("/lua/sim/entity.lua").Entity +---@class URL0001 : ACUUnit, CCommandUnit +---@field HasStealthEnh? true +---@field HasCloakEnh? true +---@field normalRange number # caches gun range to adjust the unit AI controller dummy weapon's range on layer change depending on active enhancements +---@field torpRange number # caches torpedo range to adjust the unit AI controller dummy weapon's range on layer change depending on active enhancements URL0001 = ClassUnit(ACUUnit, CCommandUnit) { Weapons = { DeathWeapon = ClassWeapon(ACUDeathWeapon) {}, RightRipper = ClassWeapon(CCannonMolecularWeapon) {}, Torpedo = ClassWeapon(CANTorpedoLauncherWeapon) {}, + ---@class MLG : CDFHeavyMicrowaveLaserGeneratorCom MLG = ClassWeapon(CDFHeavyMicrowaveLaserGeneratorCom) { DisabledFiringBones = { 'Turret_Muzzle_03' }, + ---@param self MLG + ---@param transportstate boolean SetOnTransport = function(self, transportstate) CDFHeavyMicrowaveLaserGeneratorCom.SetOnTransport(self, transportstate) - self.Trash:Add(ForkThread(self.OnTransportWatch,self)) + self.Trash:Add(ForkThread(self.OnTransportWatch, self)) end, + ---@param self MLG OnTransportWatch = function(self) while self:GetOnTransport() do self:PlayFxBeamEnd() @@ -57,10 +65,12 @@ URL0001 = ClassUnit(ACUUnit, CCommandUnit) { AutoOverCharge = ClassWeapon(CDFOverchargeWeapon) {}, }, + ---@param self URL0001 __init = function(self) ACUUnit.__init(self, 'RightRipper') end, + ---@param self URL0001 OnCreate = function(self) ACUUnit.OnCreate(self) CCommandUnit.OnCreate(self) @@ -85,6 +95,9 @@ URL0001 = ClassUnit(ACUUnit, CCommandUnit) { end end, + ---@param self URL0001 + ---@param builder Unit + ---@param layer Layer OnStopBeingBuilt = function(self, builder, layer) ACUUnit.OnStopBeingBuilt(self, builder, layer) self:SetWeaponEnabledByLabel('RightRipper', true) @@ -97,9 +110,12 @@ URL0001 = ClassUnit(ACUUnit, CCommandUnit) { self:DisableUnitIntel('Enhancement', 'Sonar') self:HideBone('Back_Upgrade', true) self:HideBone('Right_Upgrade', true) - self.Trash:Add(ForkThread(self.GiveInitialResources,self)) + self.Trash:Add(ForkThread(self.GiveInitialResources, self)) end, + ---@param self URL0001 + ---@param unitBeingBuilt Unit + ---@param order string OnStartBuild = function(self, unitBeingBuilt, order) ACUUnit.OnStartBuild(self, unitBeingBuilt, order) self.UnitBeingBuilt = unitBeingBuilt @@ -107,277 +123,367 @@ URL0001 = ClassUnit(ACUUnit, CCommandUnit) { self.BuildingUnit = true end, - CreateEnhancement = function(self, enh) - ACUUnit.CreateEnhancement(self, enh) - local bp = self.Blueprint.Enhancements[enh] - if enh == 'Teleporter' then - self:AddCommandCap('RULEUCC_Teleport') - elseif enh == 'TeleporterRemove' then - RemoveUnitEnhancement(self, 'Teleporter') - RemoveUnitEnhancement(self, 'TeleporterRemove') - self:RemoveCommandCap('RULEUCC_Teleport') - elseif enh == 'StealthGenerator' then - self:AddToggleCap('RULEUTC_StealthToggle') - self.StealthEnh = true - self:EnableUnitIntel('Enhancement', 'RadarStealth') - self:EnableUnitIntel('Enhancement', 'SonarStealth') - if not Buffs['CybranACUStealthBonus'] then - BuffBlueprint { - Name = 'CybranACUStealthBonus', - DisplayName = 'CybranACUStealthBonus', - BuffType = 'ACUSTEALTHBONUS', - Stacks = 'ALWAYS', - Duration = -1, - Affects = { - MaxHealth = { - Add = bp.NewHealth, - Mult = 1.0, - }, - Regen = { - Add = bp.NewRegenRate, - Mult = 1.0, - } + --------------------------------------------------------------------------- + --#region Enhancements + + ---@param self URL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementTeleporter = function(self, bp) + self:AddCommandCap('RULEUCC_Teleport') + end, + + ---@param self URL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementTeleporterRemove = function(self, bp) + RemoveUnitEnhancement(self, 'Teleporter') + RemoveUnitEnhancement(self, 'TeleporterRemove') + self:RemoveCommandCap('RULEUCC_Teleport') + end, + + ---@param self URL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementStealthGenerator = function(self, bp) + self:AddToggleCap('RULEUTC_StealthToggle') + self.HasStealthEnh = true + self:EnableUnitIntel('Enhancement', 'RadarStealth') + self:EnableUnitIntel('Enhancement', 'SonarStealth') + if not Buffs['CybranACUStealthBonus'] then + BuffBlueprint { + Name = 'CybranACUStealthBonus', + DisplayName = 'CybranACUStealthBonus', + BuffType = 'ACUSTEALTHBONUS', + Stacks = 'ALWAYS', + Duration = -1, + Affects = { + MaxHealth = { + Add = bp.NewHealth, + Mult = 1.0, }, - } - end - if not Buff.HasBuff(self, 'CybranACUStealthBonus') then - Buff.ApplyBuff(self, 'CybranACUStealthBonus') - end - elseif enh == 'StealthGeneratorRemove' then - self:RemoveToggleCap('RULEUTC_StealthToggle') - self:DisableUnitIntel('Enhancement', 'RadarStealth') - self:DisableUnitIntel('Enhancement', 'SonarStealth') - self.StealthEnh = nil - if Buff.HasBuff(self, 'CybranACUStealthBonus') then - Buff.RemoveBuff(self, 'CybranACUStealthBonus') - end - elseif enh == 'FAF_SelfRepairSystem' then - if not Buffs['CybranACURegenerateBonus'] then - BuffBlueprint { - Name = 'CybranACURegenerateBonus', - DisplayName = 'CybranACURegenerateBonus', - BuffType = 'ACUNANO', - Stacks = 'ALWAYS', - Duration = -1, - Affects = { - MaxHealth = { - Add = bp.NewHealth, - Mult = 1.0, - }, - Regen = { - Add = bp.NewRegenRate, - Mult = 1.0, - } + Regen = { + Add = bp.NewRegenRate, + Mult = 1.0, + } + }, + } + end + if not Buff.HasBuff(self, 'CybranACUStealthBonus') then + Buff.ApplyBuff(self, 'CybranACUStealthBonus') + end + end, + + ---@param self URL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementStealthGeneratorRemove = function(self, bp) + self:RemoveToggleCap('RULEUTC_StealthToggle') + self:DisableUnitIntel('Enhancement', 'RadarStealth') + self:DisableUnitIntel('Enhancement', 'SonarStealth') + self.HasStealthEnh = nil + if Buff.HasBuff(self, 'CybranACUStealthBonus') then + Buff.RemoveBuff(self, 'CybranACUStealthBonus') + end + end, + + ---@param self URL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementFAF_SelfRepairSystem = function(self, bp) + if not Buffs['CybranACURegenerateBonus'] then + BuffBlueprint { + Name = 'CybranACURegenerateBonus', + DisplayName = 'CybranACURegenerateBonus', + BuffType = 'ACUNANO', + Stacks = 'ALWAYS', + Duration = -1, + Affects = { + MaxHealth = { + Add = bp.NewHealth, + Mult = 1.0, }, - } - end - if not Buff.HasBuff(self, 'CybranACURegenerateBonus') then - Buff.ApplyBuff(self, 'CybranACURegenerateBonus') - end - elseif enh == 'FAF_SelfRepairSystemRemove' then - -- remove prerequisites - self:RemoveToggleCap('RULEUTC_StealthToggle') - self:DisableUnitIntel('Enhancement', 'RadarStealth') - self:DisableUnitIntel('Enhancement', 'SonarStealth') - self.StealthEnh = nil - if Buff.HasBuff(self, 'CybranACUStealthBonus') then - Buff.RemoveBuff(self, 'CybranACUStealthBonus') - end + Regen = { + Add = bp.NewRegenRate, + Mult = 1.0, + } + }, + } + end + if not Buff.HasBuff(self, 'CybranACURegenerateBonus') then + Buff.ApplyBuff(self, 'CybranACURegenerateBonus') + end + end, - -- remove repair system - if Buff.HasBuff(self, 'CybranACURegenerateBonus') then - Buff.RemoveBuff(self, 'CybranACURegenerateBonus') - end - elseif enh == 'CloakingGenerator' then - if not bp then return end - self:RemoveToggleCap('RULEUTC_StealthToggle') - self:AddToggleCap('RULEUTC_CloakToggle') - self.StealthEnh = nil - self.CloakEnh = true - self:EnableUnitIntel('Enhancement', 'Cloak') - if not Buffs['CybranACUCloakBonus'] then - BuffBlueprint { - Name = 'CybranACUCloakBonus', - DisplayName = 'CybranACUCloakBonus', - BuffType = 'ACUCLOAKBONUS', - Stacks = 'ALWAYS', - Duration = -1, - Affects = { - MaxHealth = { - Add = bp.NewHealth, - Mult = 1.0, - }, + ---@param self URL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementFAF_SelfRepairSystemRemove = function(self, bp) + -- remove prerequisites + self:RemoveToggleCap('RULEUTC_StealthToggle') + self:DisableUnitIntel('Enhancement', 'RadarStealth') + self:DisableUnitIntel('Enhancement', 'SonarStealth') + self.HasStealthEnh = nil + if Buff.HasBuff(self, 'CybranACUStealthBonus') then + Buff.RemoveBuff(self, 'CybranACUStealthBonus') + end + + -- remove repair system + if Buff.HasBuff(self, 'CybranACURegenerateBonus') then + Buff.RemoveBuff(self, 'CybranACURegenerateBonus') + end + end, + + ---@param self URL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementCloakingGenerator = function(self, bp) + self:RemoveToggleCap('RULEUTC_StealthToggle') + self:AddToggleCap('RULEUTC_CloakToggle') + self.HasStealthEnh = nil + self.HasCloakEnh = true + self:EnableUnitIntel('Enhancement', 'Cloak') + if not Buffs['CybranACUCloakBonus'] then + BuffBlueprint { + Name = 'CybranACUCloakBonus', + DisplayName = 'CybranACUCloakBonus', + BuffType = 'ACUCLOAKBONUS', + Stacks = 'ALWAYS', + Duration = -1, + Affects = { + MaxHealth = { + Add = bp.NewHealth, + Mult = 1.0, }, - } - end - if not Buff.HasBuff(self, 'CybranACUCloakBonus') then - Buff.ApplyBuff(self, 'CybranACUCloakBonus') - end - elseif enh == 'CloakingGeneratorRemove' then - -- remove prerequisites - self:RemoveToggleCap('RULEUTC_CloakToggle') - self:DisableUnitIntel('Enhancement', 'RadarStealth') - self:DisableUnitIntel('Enhancement', 'SonarStealth') - self.StealthEnh = nil - if Buff.HasBuff(self, 'CybranACUStealthBonus') then - Buff.RemoveBuff(self, 'CybranACUStealthBonus') - end - if Buff.HasBuff(self, 'CybranACURegenerateBonus') then - Buff.RemoveBuff(self, 'CybranACURegenerateBonus') - end + }, + } + end + if not Buff.HasBuff(self, 'CybranACUCloakBonus') then + Buff.ApplyBuff(self, 'CybranACUCloakBonus') + end + end, - -- remove cloak - self:RemoveToggleCap('RULEUTC_CloakToggle') - self:DisableUnitIntel('Enhancement', 'Cloak') - self.CloakEnh = nil - if Buff.HasBuff(self, 'CybranACUCloakBonus') then - Buff.RemoveBuff(self, 'CybranACUCloakBonus') - end - elseif enh == 'ResourceAllocation' then - local bpEcon = self.Blueprint.Economy - if not bp then return end - self:SetProductionPerSecondEnergy((bp.ProductionPerSecondEnergy + bpEcon.ProductionPerSecondEnergy) or 0) - self:SetProductionPerSecondMass((bp.ProductionPerSecondMass + bpEcon.ProductionPerSecondMass) or 0) - elseif enh == 'ResourceAllocationRemove' then - local bpEcon = self.Blueprint.Economy - self:SetProductionPerSecondEnergy(bpEcon.ProductionPerSecondEnergy or 0) - self:SetProductionPerSecondMass(bpEcon.ProductionPerSecondMass or 0) - elseif enh =='AdvancedEngineering' then - self.BuildBotTotal = 3 - if not bp then return end - local cat = ParseEntityCategory(bp.BuildableCategoryAdds) - self:RemoveBuildRestriction(cat) - if not Buffs['CybranACUT2BuildRate'] then - BuffBlueprint { - Name = 'CybranACUT2BuildRate', - DisplayName = 'CybranACUT2BuildRate', - BuffType = 'ACUBUILDRATE', - Stacks = 'REPLACE', - Duration = -1, - Affects = { - BuildRate = { - Add = bp.NewBuildRate - self.Blueprint.Economy.BuildRate, - Mult = 1.0, - }, - MaxHealth = { - Add = bp.NewHealth, - Mult = 1.0, - }, - Regen = { - Add = bp.NewRegenRate, - Mult = 1.0, - }, + ---@param self URL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementCloakingGeneratorRemove = function(self, bp) + -- remove prerequisites + self:RemoveToggleCap('RULEUTC_CloakToggle') + self:DisableUnitIntel('Enhancement', 'RadarStealth') + self:DisableUnitIntel('Enhancement', 'SonarStealth') + self.HasStealthEnh = nil + if Buff.HasBuff(self, 'CybranACUStealthBonus') then + Buff.RemoveBuff(self, 'CybranACUStealthBonus') + end + if Buff.HasBuff(self, 'CybranACURegenerateBonus') then + Buff.RemoveBuff(self, 'CybranACURegenerateBonus') + end + + -- remove cloak + self:RemoveToggleCap('RULEUTC_CloakToggle') + self:DisableUnitIntel('Enhancement', 'Cloak') + self.HasCloakEnh = nil + if Buff.HasBuff(self, 'CybranACUCloakBonus') then + Buff.RemoveBuff(self, 'CybranACUCloakBonus') + end + end, + + ---@param self URL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementResourceAllocation = function(self, bp) + local bpEcon = self.Blueprint.Economy + self:SetProductionPerSecondEnergy((bp.ProductionPerSecondEnergy + bpEcon.ProductionPerSecondEnergy) or 0) + self:SetProductionPerSecondMass((bp.ProductionPerSecondMass + bpEcon.ProductionPerSecondMass) or 0) + end, + + ---@param self URL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementResourceAllocationRemove = function(self, bp) + local bpEcon = self.Blueprint.Economy + self:SetProductionPerSecondEnergy(bpEcon.ProductionPerSecondEnergy or 0) + self:SetProductionPerSecondMass(bpEcon.ProductionPerSecondMass or 0) + end, + + ---@param self URL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementAdvancedEngineering = function(self, bp) + self.BuildBotTotal = 3 + local cat = ParseEntityCategory(bp.BuildableCategoryAdds) + self:RemoveBuildRestriction(cat) + if not Buffs['CybranACUT2BuildRate'] then + BuffBlueprint { + Name = 'CybranACUT2BuildRate', + DisplayName = 'CybranACUT2BuildRate', + BuffType = 'ACUBUILDRATE', + Stacks = 'REPLACE', + Duration = -1, + Affects = { + BuildRate = { + Add = bp.NewBuildRate - self.Blueprint.Economy.BuildRate, + Mult = 1.0, }, - } - end - Buff.ApplyBuff(self, 'CybranACUT2BuildRate') - elseif enh == 'AdvancedEngineeringRemove' then - self.BuildBotTotal = 2 - local buildRate = self.Blueprint.Economy.BuildRate - if not buildRate then return end - self:RestoreBuildRestrictions() - self:AddBuildRestriction(categories.CYBRAN * - (categories.BUILTBYTIER2COMMANDER + categories.BUILTBYTIER3COMMANDER)) - if Buff.HasBuff(self, 'CybranACUT2BuildRate') then - Buff.RemoveBuff(self, 'CybranACUT2BuildRate') - end - elseif enh =='T3Engineering' then - self.BuildBotTotal = 4 - if not bp then return end - local cat = ParseEntityCategory(bp.BuildableCategoryAdds) - self:RemoveBuildRestriction(cat) - if not Buffs['CybranACUT3BuildRate'] then - BuffBlueprint { - Name = 'CybranACUT3BuildRate', - DisplayName = 'CybranCUT3BuildRate', - BuffType = 'ACUBUILDRATE', - Stacks = 'REPLACE', - Duration = -1, - Affects = { - BuildRate = { - Add = bp.NewBuildRate - self.Blueprint.Economy.BuildRate, - Mult = 1.0, - }, - MaxHealth = { - Add = bp.NewHealth, - Mult = 1.0, - }, - Regen = { - Add = bp.NewRegenRate, - Mult = 1.0, - }, + MaxHealth = { + Add = bp.NewHealth, + Mult = 1.0, }, - } - end - Buff.ApplyBuff(self, 'CybranACUT3BuildRate') - elseif enh == 'T3EngineeringRemove' then + Regen = { + Add = bp.NewRegenRate, + Mult = 1.0, + }, + }, + } + end + Buff.ApplyBuff(self, 'CybranACUT2BuildRate') + end, - self.BuildBotTotal = 2 + ---@param self URL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementAdvancedEngineeringRemove = function(self, bp) + self.BuildBotTotal = 2 + local buildRate = self.Blueprint.Economy.BuildRate + if not buildRate then return end + self:RestoreBuildRestrictions() + self:AddBuildRestriction(categories.CYBRAN * + (categories.BUILTBYTIER2COMMANDER + categories.BUILTBYTIER3COMMANDER)) + if Buff.HasBuff(self, 'CybranACUT2BuildRate') then + Buff.RemoveBuff(self, 'CybranACUT2BuildRate') + end + end, - local buildRate = self.Blueprint.Economy.BuildRate - if not buildRate then return end - self:RestoreBuildRestrictions() - if Buff.HasBuff(self, 'CybranACUT3BuildRate') then - Buff.RemoveBuff(self, 'CybranACUT3BuildRate') - end - self:AddBuildRestriction(categories.CYBRAN * (categories.BUILTBYTIER2COMMANDER + categories.BUILTBYTIER3COMMANDER)) - elseif enh =='CoolingUpgrade' then - local wep = self:GetWeaponByLabel('RightRipper') - wep:ChangeMaxRadius(bp.NewMaxRadius or 30) - self.normalRange = bp.NewMaxRadius or 30 - wep:ChangeRateOfFire(bp.NewRateOfFire or 2) - local microwave = self:GetWeaponByLabel('MLG') - microwave:ChangeMaxRadius(bp.NewMaxRadius or 30) - local oc = self:GetWeaponByLabel('OverCharge') - oc:ChangeMaxRadius(bp.NewMaxRadius or 30) - local aoc = self:GetWeaponByLabel('AutoOverCharge') - aoc:ChangeMaxRadius(bp.NewMaxRadius or 30) - if not (self.Layer == 'Seabed' and self:HasEnhancement('NaniteTorpedoTube')) then - self:GetWeaponByLabel('DummyWeapon'):ChangeMaxRadius(self.normalRange) - end - elseif enh == 'CoolingUpgradeRemove' then - local wep = self:GetWeaponByLabel('RightRipper') - local wepBp = self.Blueprint.Weapon - for _, v in wepBp do - if v.Label == 'RightRipper' then - local newRange = v.MaxRadius or 22 - wep:ChangeRateOfFire(v.RateOfFire or 1) - wep:ChangeMaxRadius(newRange) - self.normalRange = newRange - self:GetWeaponByLabel('MLG'):ChangeMaxRadius(newRange) - self:GetWeaponByLabel('OverCharge'):ChangeMaxRadius(newRange) - self:GetWeaponByLabel('AutoOverCharge'):ChangeMaxRadius(newRange) - self.normalRange = newRange - if not (self.Layer == 'Seabed' and self:HasEnhancement('NaniteTorpedoTube')) then - self:GetWeaponByLabel('DummyWeapon'):ChangeMaxRadius(self.normalRange) - end - break + ---@param self URL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementT3Engineering = function(self, bp) + self.BuildBotTotal = 4 + local cat = ParseEntityCategory(bp.BuildableCategoryAdds) + self:RemoveBuildRestriction(cat) + if not Buffs['CybranACUT3BuildRate'] then + BuffBlueprint { + Name = 'CybranACUT3BuildRate', + DisplayName = 'CybranCUT3BuildRate', + BuffType = 'ACUBUILDRATE', + Stacks = 'REPLACE', + Duration = -1, + Affects = { + BuildRate = { + Add = bp.NewBuildRate - self.Blueprint.Economy.BuildRate, + Mult = 1.0, + }, + MaxHealth = { + Add = bp.NewHealth, + Mult = 1.0, + }, + Regen = { + Add = bp.NewRegenRate, + Mult = 1.0, + }, + }, + } + end + Buff.ApplyBuff(self, 'CybranACUT3BuildRate') + end, + + ---@param self URL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementT3EngineeringRemove = function(self, bp) + self.BuildBotTotal = 2 + local buildRate = self.Blueprint.Economy.BuildRate + if not buildRate then return end + self:RestoreBuildRestrictions() + if Buff.HasBuff(self, 'CybranACUT3BuildRate') then + Buff.RemoveBuff(self, 'CybranACUT3BuildRate') + end + self:AddBuildRestriction(categories.CYBRAN * (categories.BUILTBYTIER2COMMANDER + categories.BUILTBYTIER3COMMANDER)) + end, + + ---@param self URL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementCoolingUpgrade = function(self, bp) + local wep = self:GetWeaponByLabel('RightRipper') + wep:ChangeMaxRadius(bp.NewMaxRadius or 30) + self.normalRange = bp.NewMaxRadius or 30 + wep:ChangeRateOfFire(bp.NewRateOfFire or 2) + local microwave = self:GetWeaponByLabel('MLG') + microwave:ChangeMaxRadius(bp.NewMaxRadius or 30) + local oc = self:GetWeaponByLabel('OverCharge') + oc:ChangeMaxRadius(bp.NewMaxRadius or 30) + local aoc = self:GetWeaponByLabel('AutoOverCharge') + aoc:ChangeMaxRadius(bp.NewMaxRadius or 30) + if not (self.Layer == 'Seabed' and self:HasEnhancement('NaniteTorpedoTube')) then + self:GetWeaponByLabel('DummyWeapon'):ChangeMaxRadius(self.normalRange) + end + end, + + ---@param self URL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementCoolingUpgradeRemove = function(self, bp) + local wep = self:GetWeaponByLabel('RightRipper') + local wepBp = self.Blueprint.Weapon + for _, v in wepBp do + if v.Label == 'RightRipper' then + local newRange = v.MaxRadius or 22 + wep:ChangeRateOfFire(v.RateOfFire or 1) + wep:ChangeMaxRadius(newRange) + self.normalRange = newRange + self:GetWeaponByLabel('MLG'):ChangeMaxRadius(newRange) + self:GetWeaponByLabel('OverCharge'):ChangeMaxRadius(newRange) + self:GetWeaponByLabel('AutoOverCharge'):ChangeMaxRadius(newRange) + self.normalRange = newRange + if not (self.Layer == 'Seabed' and self:HasEnhancement('NaniteTorpedoTube')) then + self:GetWeaponByLabel('DummyWeapon'):ChangeMaxRadius(self.normalRange) end - end - elseif enh == 'MicrowaveLaserGenerator' then - self:SetWeaponEnabledByLabel('MLG', true) - elseif enh == 'MicrowaveLaserGeneratorRemove' then - self:SetWeaponEnabledByLabel('MLG', false) - elseif enh == 'NaniteTorpedoTube' then - local enhbp = self.Blueprint.Enhancements[enh] - self:SetWeaponEnabledByLabel('Torpedo', true) - self:SetIntelRadius('Sonar', enhbp.NewSonarRadius or 60) - self:EnableUnitIntel('Enhancement', 'Sonar') - if self.Layer == 'Seabed' then - self:GetWeaponByLabel('DummyWeapon'):ChangeMaxRadius(self.torpRange) - end - elseif enh == 'NaniteTorpedoTubeRemove' then - local bpIntel = self.Blueprint.Intel - self:SetWeaponEnabledByLabel('Torpedo', false) - self:SetIntelRadius('Sonar', bpIntel.SonarRadius or 26) - self:DisableUnitIntel('Enhancement', 'Sonar') - if self.Layer == 'Seabed' then - self:GetWeaponByLabel('DummyWeapon'):ChangeMaxRadius(self.normalRange) + break end end end, + ---@param self URL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementMicrowaveLaserGenerator = function(self, bp) + self:SetWeaponEnabledByLabel('MLG', true) + end, + + ---@param self URL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementMicrowaveLaserGeneratorRemove = function(self, bp) + self:SetWeaponEnabledByLabel('MLG', false) + end, + + ---@param self URL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementNaniteTorpedoTube = function(self, bp) + self:SetWeaponEnabledByLabel('Torpedo', true) + self:SetIntelRadius('Sonar', bp.NewSonarRadius or 60) + self:EnableUnitIntel('Enhancement', 'Sonar') + if self.Layer == 'Seabed' then + self:GetWeaponByLabel('DummyWeapon'):ChangeMaxRadius(self.torpRange) + end + end, + + ---@param self URL0001 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementNaniteTorpedoTubeRemove = function(self, bp) + local bpIntel = self.Blueprint.Intel + self:SetWeaponEnabledByLabel('Torpedo', false) + self:SetIntelRadius('Sonar', bpIntel.SonarRadius or 26) + self:DisableUnitIntel('Enhancement', 'Sonar') + if self.Layer == 'Seabed' then + self:GetWeaponByLabel('DummyWeapon'):ChangeMaxRadius(self.normalRange) + end + end, + + ---@param self URL0001 + ---@param enh CybranACUEnhancementBuffType + CreateEnhancement = function(self, enh) + ACUUnit.CreateEnhancement(self, enh) + + local bp = self.Blueprint.Enhancements[enh] + + if not bp then return end + + local ref = 'ProcessEnhancement' .. enh + local handler = self[ref] + if handler then + handler(self, bp) + else + WARN("Missing enhancement: ", enh, " for unit: ", self:GetUnitId(), " note that the function name should be called: ", ref) + end + end, + + --#endregion + -- Intel IntelEffects = { Cloak = { @@ -424,9 +530,11 @@ URL0001 = ClassUnit(ACUUnit, CCommandUnit) { }, }, + ---@param self URL0001 + ---@param intel? IntelType OnIntelEnabled = function(self, intel) ACUUnit.OnIntelEnabled(self, intel) - if self.CloakEnh and self:IsIntelEnabled('Cloak') then + if self.HasCloakEnh and self:IsIntelEnabled('Cloak') then self:SetEnergyMaintenanceConsumptionOverride(self.Blueprint.Enhancements['CloakingGenerator'].MaintenanceConsumptionPerSecondEnergy or 0) self:SetMaintenanceConsumptionActive() @@ -434,7 +542,7 @@ URL0001 = ClassUnit(ACUUnit, CCommandUnit) { self.IntelEffectsBag = {} self:CreateTerrainTypeEffects(self.IntelEffects.Cloak, 'FXIdle', self.Layer, nil, self.IntelEffectsBag) end - elseif self.StealthEnh and self:IsIntelEnabled('RadarStealth') and self:IsIntelEnabled('SonarStealth') then + elseif self.HasStealthEnh and self:IsIntelEnabled('RadarStealth') and self:IsIntelEnabled('SonarStealth') then self:SetEnergyMaintenanceConsumptionOverride(self.Blueprint.Enhancements['StealthGenerator'].MaintenanceConsumptionPerSecondEnergy or 0) self:SetMaintenanceConsumptionActive() @@ -445,19 +553,25 @@ URL0001 = ClassUnit(ACUUnit, CCommandUnit) { end end, + ---@param self URL0001 + ---@param intel? IntelType OnIntelDisabled = function(self, intel) ACUUnit.OnIntelDisabled(self, intel) if self.IntelEffectsBag then EffectUtil.CleanupEffectBag(self, 'IntelEffectsBag') self.IntelEffectsBag = nil end - if self.CloakEnh and not self:IsIntelEnabled('Cloak') then + if self.HasCloakEnh and not self:IsIntelEnabled('Cloak') then self:SetMaintenanceConsumptionInactive() - elseif self.StealthEnh and not self:IsIntelEnabled('RadarStealth') and not self:IsIntelEnabled('SonarStealth') then + elseif self.HasStealthEnh and not self:IsIntelEnabled('RadarStealth') and not self:IsIntelEnabled('SonarStealth') then self:SetMaintenanceConsumptionInactive() end end, + --- Makes sure the ACU walks into the correct range for the target when it has/doesn't have the torpedo enhancement. + ---@param self URL0001 + ---@param new any + ---@param old any OnLayerChange = function(self, new, old) ACUUnit.OnLayerChange(self, new, old) if self:GetWeaponByLabel('DummyWeapon') == nil then return end @@ -470,3 +584,9 @@ URL0001 = ClassUnit(ACUUnit, CCommandUnit) { } TypeClass = URL0001 + +--#region backwards compatibility + +local Entity = import("/lua/sim/entity.lua").Entity + +--#endregion From d8db806a0592ecb11523dd920370760b5972dcd2 Mon Sep 17 00:00:00 2001 From: "(Jip) Willem Wijnia" Date: Tue, 12 Nov 2024 20:53:06 +0100 Subject: [PATCH 16/33] Fix depth charges not applying the `ProjectilesToDeflect` blueprint field (#6507) --- changelog/snippets/fix.6507.md | 3 +++ lua/sim/Projectile.lua | 13 ++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 changelog/snippets/fix.6507.md diff --git a/changelog/snippets/fix.6507.md b/changelog/snippets/fix.6507.md new file mode 100644 index 0000000000..dc5b9a3bb9 --- /dev/null +++ b/changelog/snippets/fix.6507.md @@ -0,0 +1,3 @@ +- (#6507) Fix depth charges not applying the ProjectilesToDeflect blueprint field + +As a result, all depth charges would also deflect up to 3 projectiles. Mostly applies to torpedo defenses. diff --git a/lua/sim/Projectile.lua b/lua/sim/Projectile.lua index 7f4b44fec1..7db39fcf32 100644 --- a/lua/sim/Projectile.lua +++ b/lua/sim/Projectile.lua @@ -94,6 +94,10 @@ local VectorCached = Vector(0, 0, 0) ---@field Launcher Unit ---@field OriginalTarget? Unit ---@field DamageData WeaponDamageTable +---@field MyDepthCharge? DepthCharge # If weapon blueprint has a (valid) `DepthCharge` field +---@field MyFlare? Flare # If weapon blueprint has a (valid) `Flare` field +---@field MyUpperFlare? Flare # If weapon blueprint has a (valid) `Flare` field that wants to be stacked +---@field MyLowerFlare? Flare # If weapon blueprint has a (valid) `Flare` field that wants to be stacked ---@field CreatedByWeapon Weapon ---@field IsRedirected? boolean ---@field InnerRing? NukeAOE @@ -821,12 +825,15 @@ Projectile = ClassProjectile(ProjectileMethods, DebugProjectileComponent) { AddDepthCharge = function(self, blueprint) if not blueprint then return end if not blueprint.Radius then return end - self.MyDepthCharge = DepthCharge { + + ---@type DepthChargeSpec + local depthChargeSpec = { Owner = self, Radius = blueprint.Radius or 10, - DepthCharge = blueprint.ProjectilesToDeflect + ProjectilesToDeflect = blueprint.ProjectilesToDeflect } - self.Trash:Add(self.MyDepthCharge) + + self.MyDepthCharge = self.Trash:Add(DepthCharge(depthChargeSpec)) end, --- Called by Lua to create the impact effects From 55b6598f99940c19f132674fb8eac1e23eca3fd6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Nov 2024 20:53:37 +0100 Subject: [PATCH 17/33] Bump rexml from 3.3.6 to 3.3.9 in /docs (#6496) --- docs/Gemfile.lock | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index 3f3554cfee..1f39d8f24f 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -69,8 +69,7 @@ GEM rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) - rexml (3.3.6) - strscan + rexml (3.3.9) rouge (4.2.1) safe_yaml (1.0.5) sass-embedded (1.75.0-arm64-darwin) @@ -79,7 +78,6 @@ GEM google-protobuf (>= 3.25, < 5.0) sass-embedded (1.75.0-x86_64-linux-gnu) google-protobuf (>= 3.25, < 5.0) - strscan (3.1.0) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) unicode-display_width (2.5.0) From 5fa3fafee7ca834d2f54f19f38d0115570d35443 Mon Sep 17 00:00:00 2001 From: Aaron Warner <34614077+relent0r@users.noreply.github.com> Date: Wed, 13 Nov 2024 09:00:11 +1300 Subject: [PATCH 18/33] Fix `GetAngleInBetween` refactor (#6525) --------- Co-authored-by: lL1l1 <82986251+lL1l1@users.noreply.github.com> --- changelog/snippets/other.6438.md | 2 +- lua/utilities.lua | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/changelog/snippets/other.6438.md b/changelog/snippets/other.6438.md index 983cdb4dc4..d9e6646414 100644 --- a/changelog/snippets/other.6438.md +++ b/changelog/snippets/other.6438.md @@ -1,4 +1,4 @@ -- (#5061, #6438, #6527) Add metamethods and utility functions for Vectors and Quaternions to simplify and clean up the code involving operations with them. +- (#5061, #6438, #6527, #6525) Add metamethods and utility functions for Vectors and Quaternions to simplify and clean up the code involving operations with them. - This **removes** the file `/lua/shared/quaternions.lua`, which was added in #4768 (Mar 4, 2023), so mods that use that file will have to be updated. - The metamethods (defined globally in `/lua/system/utils.lua`) include: - Vector/Vector2 addition/subtraction/negation diff --git a/lua/utilities.lua b/lua/utilities.lua index 4423c723ed..0b33e22d8e 100644 --- a/lua/utilities.lua +++ b/lua/utilities.lua @@ -406,12 +406,11 @@ end ---@param v2 Vector ---@return number function GetAngleInBetween(v1, v2) - local x1, y1, z2 = v1[1], v1[2], v1[3] + local x1, y1, z1 = v1[1], v1[2], v1[3] local x2, y2, z2 = v2[1], v2[2], v2[3] -- arccos((v1 . v2) / (|v1| |v2|)) - local z2Sq = z2 * z2 - local dot = x1*x2 + y1*y2 + z2Sq - local len2 = MathSqrt((x1*x1 + y1*y1 + z2Sq) * (x2*x2 + y2*y2 + z2Sq)) + local dot = x1 * x2 + y1 * y2 + z1 * z2 + local len2 = MathSqrt((x1 * x1 + y1 * y1 + z1 * z1) * (x2 * x2 + y2 * y2 + z2 * z2)) return MathACos(dot / len2) * 180 / math.pi end From 191576f6a3590fb8e2c65477bd33d20524852fb9 Mon Sep 17 00:00:00 2001 From: lL1l1 <82986251+lL1l1@users.noreply.github.com> Date: Wed, 13 Nov 2024 00:30:08 -0800 Subject: [PATCH 19/33] Add a sim utility function to draw bones (#6477) In particular useful to debug collision beams. --- changelog/snippets/other.6477.md | 1 + engine/Sim/Entity.lua | 9 ++++----- lua/SimUtils.lua | 25 +++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 5 deletions(-) create mode 100644 changelog/snippets/other.6477.md diff --git a/changelog/snippets/other.6477.md b/changelog/snippets/other.6477.md new file mode 100644 index 0000000000..32bce33621 --- /dev/null +++ b/changelog/snippets/other.6477.md @@ -0,0 +1 @@ +- (#6477) Add the function `DrawBone(entity, bone, length)` to `SimUtils.lua`. diff --git a/engine/Sim/Entity.lua b/engine/Sim/Entity.lua index 40dfa49c25..978a9abffc 100644 --- a/engine/Sim/Entity.lua +++ b/engine/Sim/Entity.lua @@ -171,12 +171,11 @@ end function Entity:GetBoneCount() end ---- Returns separate three numbers representing the roll, pitch, yaw of the bone ----@see EulerToQuaternion(roll, pitch, yaw) if you need a quaternion instead +--- Returns three separate numbers representing the X, Y, Z direction vector of the bone ---@param bone Bone ----@return number roll ----@return number pitch ----@return number yaw +---@return number x +---@return number y +---@return number z function Entity:GetBoneDirection(bone) end diff --git a/lua/SimUtils.lua b/lua/SimUtils.lua index 558206ece8..66d92f082b 100644 --- a/lua/SimUtils.lua +++ b/lua/SimUtils.lua @@ -767,3 +767,28 @@ function OnAllianceResult(resultData) end import("/lua/simplayerquery.lua").AddResultListener("OfferAlliance", OnAllianceResult) + +local vectorCross = import('/lua/utilities.lua').Cross +local upVector = Vector(0, 1, 0) + +--- Draw XYZ axes of an entity's bone for one tick +---@param entity moho.entity_methods +---@param bone Bone +---@param length number? # length of axes, defaults to 0.2 +function DrawBone(entity, bone, length) + if not length then length = 0.2 end + + local pos = entity:GetPosition(bone) + local dirX, dirY, dirZ = entity:GetBoneDirection(bone) + + local forward = Vector(dirX, dirY, dirZ) + local left = vectorCross(upVector, forward) + local up = vectorCross(forward, left) + + -- X axis + DrawLine(pos, pos + left * length, 'FF0000') + -- Y axis + DrawLine(pos, pos + up * length, '00ff00') + -- Z axis + DrawLine(pos, pos + forward * length, '0000ff') +end From 454dfc400b72ec0a653184b358cf6013fa1b6d67 Mon Sep 17 00:00:00 2001 From: lL1l1 <82986251+lL1l1@users.noreply.github.com> Date: Fri, 15 Nov 2024 15:16:22 -0800 Subject: [PATCH 20/33] Remove rounding in weapon tracking radius sanitization (#6536) Fixes a bug where most weapons got 1.0 tracking radius because they were set to 1.05 but floored to 1.0. --- changelog/snippets/fix.6536.md | 5 +++++ engine/Core/Blueprints/UnitBlueprint.lua | 1 + lua/system/blueprints-weapons.lua | 13 ++++++------- 3 files changed, 12 insertions(+), 7 deletions(-) create mode 100644 changelog/snippets/fix.6536.md diff --git a/changelog/snippets/fix.6536.md b/changelog/snippets/fix.6536.md new file mode 100644 index 0000000000..6042c245f0 --- /dev/null +++ b/changelog/snippets/fix.6536.md @@ -0,0 +1,5 @@ +- (#6536) Fix the tracking radius for unit weapons being floored to the nearest tenth, which made units not track targets that are near the outside of their range. + - Mobile unit weapons: 1.0x of weapon range -> 1.05x + - Anti-air weapons: 1.10x -> 1.15x + - Bomber weapons: 1.2x -> 1.25x + - Structure weapons: 1x -> 1x diff --git a/engine/Core/Blueprints/UnitBlueprint.lua b/engine/Core/Blueprints/UnitBlueprint.lua index 4d3783ec71..64095e33f0 100644 --- a/engine/Core/Blueprints/UnitBlueprint.lua +++ b/engine/Core/Blueprints/UnitBlueprint.lua @@ -145,6 +145,7 @@ ---@see SetAutoMode ---@field InitialAutoMode boolean --- unit should unpack before firing weapon +--- Engine sets tracking radius to 1x, calls OnLostTarget when given a move order, and OnGotTarget only when not moving ---@field NeedUnpack boolean --- this muliplier is applied when a staging platform is refueling an air unit ---@field RefuelingMultiplier number diff --git a/lua/system/blueprints-weapons.lua b/lua/system/blueprints-weapons.lua index d85757fa18..ef9a5044c8 100644 --- a/lua/system/blueprints-weapons.lua +++ b/lua/system/blueprints-weapons.lua @@ -1,11 +1,10 @@ - local weaponTargetCheckUpperLimit = 6000 ---@param unit UnitBlueprint ---@param weapon WeaponBlueprint ---@param projectile? ProjectileBlueprint local function ProcessWeapon(unit, weapon, projectile) - -- pre-compute flags + -- pre-compute flags local isAir = false local isStructure = false local isBomber = false @@ -81,7 +80,7 @@ local function ProcessWeapon(unit, weapon, projectile) end end - -- process target tracking radius + -- process target tracking radius -- if it is set then we use that - allows us to make adjustments as we see fit if weapon.TrackingRadius == nil then @@ -99,7 +98,7 @@ local function ProcessWeapon(unit, weapon, projectile) end -- add significant target checking radius for bombers - if isBomber then + if isBomber then weapon.TrackingRadius = 1.25 end end @@ -112,8 +111,8 @@ local function ProcessWeapon(unit, weapon, projectile) -- by default, do not recheck targets as that is expensive when a lot of units are stacked on top of another weapon.AlwaysRecheckTarget = false - -- allow - if weapon.RangeCategory == 'UWRC_DirectFire' or + -- allow + if weapon.RangeCategory == 'UWRC_DirectFire' or weapon.RangeCategory == "UWRC_IndirectFire" or weapon.MaxRadius > 50 and (weapon.RangeCategory ~= "UWRC_AntiNavy") then weapon.AlwaysRecheckTarget = true @@ -137,8 +136,8 @@ local function ProcessWeapon(unit, weapon, projectile) weapon.AlwaysRecheckTarget = false end + -- Floor target check interval to ticks weapon.TargetCheckInterval = 0.1 * math.floor(10 * weapon.TargetCheckInterval) - weapon.TrackingRadius = 0.1 * math.floor(10 * weapon.TrackingRadius) end ---@param allBlueprints BlueprintsTable From 8d5096b2fe11d3b7e54c8f1db844f23ae9ef257f Mon Sep 17 00:00:00 2001 From: G C <37224614+Hdt80bro@users.noreply.github.com> Date: Sat, 16 Nov 2024 00:46:10 -0800 Subject: [PATCH 21/33] Fix various issues with the recall mechanic (#6396) --- lua/aibrain.lua | 1 + lua/shared/RecallParams.lua | 2 + lua/sim/Recall.lua | 236 +++++++++++++++++++++--------------- lua/ui/game/recall.lua | 56 +++++++-- 4 files changed, 185 insertions(+), 110 deletions(-) diff --git a/lua/aibrain.lua b/lua/aibrain.lua index 773b37be1b..1427408d7c 100644 --- a/lua/aibrain.lua +++ b/lua/aibrain.lua @@ -436,6 +436,7 @@ AIBrain = Class(FactoryManagerBrainComponent, StatManagerBrainComponent, JammerM import("/lua/simutils.lua").UpdateUnitCap(self:GetArmyIndex()) import("/lua/simping.lua").OnArmyDefeat(self:GetArmyIndex()) + import("/lua/sim/Recall.lua").OnArmyDefeat(self:GetArmyIndex()) local function KillArmy() local shareOption = ScenarioInfo.Options.Share diff --git a/lua/shared/RecallParams.lua b/lua/shared/RecallParams.lua index fac40d18c7..95346192c1 100644 --- a/lua/shared/RecallParams.lua +++ b/lua/shared/RecallParams.lua @@ -14,6 +14,8 @@ TeamVoteCooldown = 1 * 60 * 10 --- ticks that the recall vote is open (30 seconds) VoteTime = 30 * 10 +---@param acceptanceVotes number +---@param totalVotes number function RecallRequestAccepted(acceptanceVotes, totalVotes) if totalVotes <= 3 then return acceptanceVotes >= totalVotes diff --git a/lua/sim/Recall.lua b/lua/sim/Recall.lua index 0af1cca947..6775a7e577 100644 --- a/lua/sim/Recall.lua +++ b/lua/sim/Recall.lua @@ -9,8 +9,8 @@ doscript "/lua/shared/RecallParams.lua" local SyncAnnouncement = import("/lua/simdiplomacy.lua").SyncAnnouncement - ----@alias CannotRecallReason false +---@alias CannotRecallReason +---| false ---| "active" ---| "ai" ---| "gate" @@ -19,7 +19,6 @@ local SyncAnnouncement = import("/lua/simdiplomacy.lua").SyncAnnouncement ---| "vote" ---| "observer" - function init() -- setup sim recall state in the brains local playerCooldown = PlayerGateCooldown - PlayerRequestCooldown @@ -34,42 +33,22 @@ function init() end function OnArmyChange() - local focus = GetFocusArmy() - if focus == -1 then + if GetFocusArmy() == -1 then SyncCancelRecallVote() SyncRecallStatus() - return - end - local teamSize = 0 - local yes, no = 0, 0 - local votingThreadBrain - for index, brain in ArmyBrains do - if IsAlly(focus, index) and not ArmyIsCivilian(index) then - -- Found a voting thread. We really do need a better way to handle team data... - teamSize = teamSize + 1 - if brain.Vote ~= nil then - if brain.Vote then - yes = yes + 1 - else - no = no + 1 - end - end - if brain.recallVotingThread then - votingThreadBrain = brain - end - end + else + ResyncRecallVoting() end - if votingThreadBrain then - Sync.RecallRequest = { - StartTime = votingThreadBrain.RecallVoteStartTime, - Open = VoteTime * 0.1, - Blocks = teamSize, - Yes = yes, - No = no, - CanVote = GetArmyBrain(focus).Vote ~= nil, - } +end + +---@param army integer +function OnArmyDefeat(army) + local focus = GetFocusArmy() + if focus ~= -1 and IsAlly(army, focus) then + -- the rest of the code knows to ignore defeated players, just resync so the + -- UI can update the number of blocks + ResyncRecallVoting() end - SyncRecallStatus() end ---@param data {From: number, To: number} @@ -79,10 +58,7 @@ function OnAllianceChange(data) local oldTeam = {} local votingThreadBrain for index, ally in ArmyBrains do - if (IsAlly(armyFrom, index) or IsAlly(armyTo, index)) - and not ally:IsDefeated() - and not ArmyIsCivilian(index) - then + if (IsAlly(armyFrom, index) or IsAlly(armyTo, index)) and not ArmyIsCivilian(index) then oldTeamSize = oldTeamSize + 1 oldTeam[oldTeamSize] = ally.Nickname -- Found a voting thread. We really do need a better way to handle team data... @@ -93,7 +69,7 @@ function OnAllianceChange(data) end if votingThreadBrain then SPEW("Canceling recall voting for team " .. table.concat(oldTeam, ", ") .. " due to alliance break") - votingThreadBrain.VoteCancelled = true + votingThreadBrain.RecallVoteCancelled = true ResumeThread(votingThreadBrain.recallVotingThread) if IsAlly(votingThreadBrain, GetFocusArmy()) then SyncCancelRecallVote() @@ -103,12 +79,13 @@ function OnAllianceChange(data) end + ---@param lastTeamVote number ---@param lastPlayerRequest number ---@param playerGatein? number ---@return CannotRecallReason CannotRecallReason ---@return number? cooldown -function RecallRequestCooldown(lastTeamVote, lastPlayerRequest, playerGatein) +local function RecallRequestCooldown(lastTeamVote, lastPlayerRequest, playerGatein) -- note that this doesn't always return the reason that currently has the longest cooldown, it -- returns the more "fundamental" one (i.e. the reason whose base cooldown is longest) -- this is more useful in reporting the reason, and isn't a problem as the reason checker is a loop @@ -134,10 +111,10 @@ end ---@return CannotRecallReason ---@return number? cooldown no timeout/cooldown if absent function ArmyRecallRequestCooldown(army) - if army == -1 then + local brain = GetArmyBrain(army) + if army == -1 or brain:IsDefeated() then return "observer" end - local brain = GetArmyBrain(army) if ScenarioInfo.RecallDisabled then return "scenario" end @@ -172,12 +149,12 @@ local function RecallVotingThread(requestingArmy) WaitTicks(VoteTime) -- may be interrupted if the vote closes or is canceled by an alliance break local focus = GetFocusArmy() - if requestingBrain.VoteCancelled then + if requestingBrain.RecallVoteCancelled then if focus ~= -1 and IsAlly(requestingArmy, focus) then SyncCancelRecallVote() SyncRecallStatus() end - requestingBrain.VoteCancelled = nil + requestingBrain.RecallVoteCancelled = nil requestingBrain.RecallVoteStartTime = nil requestingBrain.recallVotingThread = nil return @@ -185,19 +162,29 @@ local function RecallVotingThread(requestingArmy) local gametick = GetGameTick() local yesVotes = 0 + local noVotes = 0 local teamSize = 0 local team = {} for index, brain in ArmyBrains do - if not brain:IsDefeated() and IsAlly(requestingArmy, brain.Army) and not ArmyIsCivilian(index) then + if not IsAlly(requestingArmy, brain.Army) or ArmyIsCivilian(index) then + continue + end + + if not brain:IsDefeated() then teamSize = teamSize + 1 team[teamSize] = brain - if brain.RecallVote then - yesVotes = yesVotes + 1 + if brain.RecallVote ~= nil then + if brain.RecallVote then + yesVotes = yesVotes + 1 + else + noVotes = noVotes + 1 + end end - brain.RecallVote = nil brain.LastRecallVoteTime = gametick end + brain.RecallVote = nil -- make sure defeated players get reset too end + -- this function is found in the recall params file, for those looking local recallPassed = RecallRequestAccepted(yesVotes, teamSize) if focus ~= -1 and IsAlly(focus, requestingArmy) then @@ -210,19 +197,22 @@ local function RecallVotingThread(requestingArmy) Team = requestingBrain.Nickname, } end + local listTeam = team[1].Nickname for i = 2, teamSize do listTeam = listTeam .. ", " .. team[i].Nickname end + local msgEnding = yesVotes .. " to " .. noVotes .. " [" .. (teamSize - yesVotes - noVotes) .. " abstained] )" if recallPassed then - SPEW("Recalling team " .. listTeam .. " at the request of " .. requestingBrain.Nickname .. " (vote passed " .. yesVotes .. " to " .. (teamSize - yesVotes ) .. ")") + SPEW("Recalling team " .. listTeam .. " at the request of " .. requestingBrain.Nickname .. " (vote passed " .. msgEnding) for _, brain in team do brain:RecallAllCommanders() end else - SPEW("Not recalling team " .. listTeam .. " (vote failed " .. yesVotes .. " to " .. (teamSize - yesVotes ) .. ")") + SPEW("Not recalling team " .. listTeam .. " (vote failed " .. msgEnding) requestingBrain.LastRecallRequestTime = gametick end + if focus ~= -1 and IsAlly(requestingArmy, focus) then -- update UI once the cooldown dissipates SyncRecallStatus() @@ -340,7 +330,7 @@ function SetRecallVote(data) return end if teammates > 0 then - SPEW("Recall request from " .. brain.Nickname .. " for " .. table.concat(team, ',')) + SPEW("Recall request from " .. brain.Nickname .. " for " .. table.concat(team, ", ")) else SPEW("Recalling " .. brain.Nickname) end @@ -353,95 +343,137 @@ function SetRecallVote(data) brain.RecallVote = vote -- if the vote will already be decided with this vote, close the voting session - if not lastVote and ( - vote and RecallRequestAccepted(likeVotes + 1, teammates) or -- will succeed with our vote - not vote and not RecallRequestAccepted(teammates - (likeVotes + 1), teammates) -- won't ever be able to succeed - ) then - lastVote = true + if not lastVote then + if vote then + -- will succeed with our vote + lastVote = RecallRequestAccepted(likeVotes + 1, teammates + 1) + else + -- won't ever be able to succeed + -- teammates - votes against = teammates that could vote for recall + lastVote = not RecallRequestAccepted(teammates + 1 - (likeVotes + 1), teammates + 1) + end end ArmyVoteRecall(army, vote, lastVote) end end +-------------------- +--#region Sync +-------------------- + +local function GetRecallSyncTable() + local sync = Sync.RecallRequest + if not sync then + sync = {} + Sync.RecallRequest = sync + end + return sync +end + +function ResyncRecallVoting() + local focus = GetFocusArmy() + local teamSize = 0 + local yes, no = 0, 0 + local votingThreadBrain + local retainBlocks = false + for index, brain in ArmyBrains do + if IsAlly(focus, index) and not ArmyIsCivilian(index) then + -- Found a voting thread. We really do need a better way to handle team data... + if brain.recallVotingThread then + votingThreadBrain = brain + if brain:IsDefeated() then + retainBlocks = true + end + end + -- it's possible a defeated player could have been the one to initiate the vote but + -- they don't count for votes + if brain:IsDefeated() then + continue + end + teamSize = teamSize + 1 + if brain.RecallVote ~= nil then + if brain.RecallVote then + yes = yes + 1 + else + no = no + 1 + end + end + end + end + if votingThreadBrain then + -- keep the block layout in the edge-case that there are 3 (or more) players + -- and the original requester is defeated so there are only 2 players - both + -- could still need to vote so the confirmation layout is inappropriate + if teamSize <= 2 and not retainBlocks then + teamSize = nil + end + + local focusBrain = GetArmyBrain(focus) + + -- no need to add changes from `GetRecallSyncTable`, we need to reset everything anyway + Sync.RecallRequest = { + StartTime = votingThreadBrain.RecallVoteStartTime * 0.1, -- convert ticks to seconds + Open = VoteTime * 0.1, -- convert ticks to seconds + Blocks = teamSize, + Yes = yes, + No = no, + CanVote = focusBrain.RecallVote == nil and not focusBrain:IsDefeated(), + } + end + SyncRecallStatus() +end + ---@param reason CannotRecallReason function SyncCannotRequestRecall(reason) - local recallSync = Sync.RecallRequest - if not recallSync then - Sync.RecallRequest = {CannotRequest = reason} - else - recallSync.CannotRequest = reason - end + GetRecallSyncTable().CannotRequest = reason end ---@param result boolean function SyncCloseRecallVote(result) - local recallSync = Sync.RecallRequest - if not recallSync then - Sync.RecallRequest = {Close = result} - else - recallSync.Close = result - end + GetRecallSyncTable().Close = result end function SyncCancelRecallVote() - local recallSync = Sync.RecallRequest - if not recallSync then - Sync.RecallRequest = {Cancel = true} - else - recallSync.Cancel = true - end + GetRecallSyncTable().Cancel = true end ---@param vote boolean function SyncRecallVote(vote) - local recallSync = Sync.RecallRequest - if not recallSync then - recallSync = {} - Sync.RecallRequest = recallSync - end + local sync = GetRecallSyncTable() if vote then - recallSync.Yes = (recallSync.Yes or 0) + 1 + sync.Yes = (sync.Yes or 0) + 1 else - recallSync.No = (recallSync.No or 0) + 1 + sync.No = (sync.No or 0) + 1 end end ---@param teamSize number ---@param army number function SyncOpenRecallVote(teamSize, army) - local recallSync = Sync.RecallRequest - if not recallSync then - recallSync = {} - Sync.RecallRequest = recallSync - end + local sync = GetRecallSyncTable() local focus = GetFocusArmy() - recallSync.Open = VoteTime * 0.1 - recallSync.CanVote = focus ~= -1 and army ~= focus - recallSync.Blocks = teamSize + sync.Open = VoteTime * 0.1 -- convert ticks to seconds + sync.CanVote = focus ~= -1 and army ~= focus and not GetArmyBrain(focus):IsDefeated() + if teamSize > 2 then + sync.Blocks = teamSize + end end local UserRecallStatusThread local function SyncRecallStatusThread() local reason, cooldown = ArmyRecallRequestCooldown(GetFocusArmy()) - while reason do + while cooldown do SyncCannotRequestRecall(reason) - if not cooldown then - UserRecallStatusThread = nil - return - end + -- may be interrupted for various reasons, such as the focus army changing -- this will be fine, we'll pick up the proper cooldown reason anyway and loop again - if cooldown < 1 then - WaitTicks(1) - else - WaitTicks(cooldown) - end + WaitTicks(math.max(1, cooldown)) reason, cooldown = ArmyRecallRequestCooldown(GetFocusArmy()) end - SyncCannotRequestRecall(false) + SyncCannotRequestRecall(reason) UserRecallStatusThread = nil end @@ -452,3 +484,5 @@ function SyncRecallStatus() UserRecallStatusThread = ForkThread(SyncRecallStatusThread) end end + +--#endregion diff --git a/lua/ui/game/recall.lua b/lua/ui/game/recall.lua index 1cc5ed0c5d..03585d71d3 100644 --- a/lua/ui/game/recall.lua +++ b/lua/ui/game/recall.lua @@ -66,6 +66,18 @@ function ToggleControl() end end +---@class RecallSyncData +---@field StartTime number # When the recall vote started sim-side in seconds +---@field Open number # Duration of the recall vote in seconds +---@field Blocks number? # number of voters. `nil` if the block count does not need to be updated, due to the edge case of a 2 player team where neither player has voted due to the vote requester's defeat +---@field Yes number # number of Yes votes +---@field No number # number of No votes +---@field CanVote boolean # Whether or not the focused army has voted or is defeated +---@field Cancel true? +---@field CannotRequest CannotRecallReason +---@field Close boolean # boolean is the recall result + +---@param data RecallSyncData function RequestHandler(data) if data.CannotRequest ~= nil then import("/lua/ui/game/diplomacy.lua").SetCannotRequestRecallReason(data.CannotRequest) @@ -168,15 +180,15 @@ RecallPanel = ClassUI(NinePatch.NinePatch) { local currentBlocks = votes.blocks if blocks ~= currentBlocks then votes.blocks = blocks - for i = currentBlocks, 1, -1 do + for i = (currentBlocks or 1), 1, -1 do local block = votes[i] if block then block:Destroy() end votes[i] = nil end - if blocks > 2 then - local panelWidth = votes.Width() + if blocks then + local panelWidth = votes.Width() / LayoutHelpers.GetPixelScaleFactor() -- pre-unmultiply scale factor local width = math.floor(panelWidth / blocks) local offsetX = math.floor((panelWidth - blocks * width) * 0.5) - width for i = 1, blocks do @@ -200,6 +212,18 @@ RecallPanel = ClassUI(NinePatch.NinePatch) { end -- manual dirtying of the lazyvar votes.Height[1] = nil + elseif currentBlocks then + local function SetTextures(vote, filename) + vote._left:SetTexture(UIUtil.UIFile(filename .. "_bmp_l.dds")) + vote._middle:SetTexture(UIUtil.UIFile(filename .. "_bmp_m.dds")) + vote._right:SetTexture(UIUtil.UIFile(filename .. "_bmp_r.dds")) + end + + -- reset the status of existing blocks + for i = 1, currentBlocks do + votes[i].cast = nil + SetTextures(votes[i], "/game/recall-panel/recall-vote") + end end end, @@ -306,6 +330,7 @@ RecallPanel = ClassUI(NinePatch.NinePatch) { self.reviewResultsThread = nil end, self) end + self.label:SetText(LOC("Ready for recall")) end, CancelVote = function(self) @@ -323,7 +348,7 @@ RecallPanel = ClassUI(NinePatch.NinePatch) { self.startTime:Set(-9999) -- make sure the OnFrame animation ends if self.reviewResultsThread then -- continue the OnSecond animation if it exists - coroutine.resume(self.reviewResultsThread) + ResumeThread(self.reviewResultsThread) else -- otherwise, create our own result reviewing handler self.reviewResultsThread = ForkThread(function(self) @@ -342,12 +367,15 @@ RecallPanel = ClassUI(NinePatch.NinePatch) { AddVotes = function(self, yes, no) local votes = self.votes - if votes.blocks < 3 then return end + if not votes.blocks then return end + local function SetTextures(vote, filename) vote._left:SetTexture(UIUtil.UIFile(filename .. "_bmp_l.dds")) vote._middle:SetTexture(UIUtil.UIFile(filename .. "_bmp_m.dds")) vote._right:SetTexture(UIUtil.UIFile(filename .. "_bmp_r.dds")) end + + -- get where these new votes should be added on top of existing ones local index = 1 for i = 1, votes.blocks do if not votes[i].cast then @@ -355,6 +383,8 @@ RecallPanel = ClassUI(NinePatch.NinePatch) { break end end + + -- add the new votes if yes then for _ = 1, yes do local vote = votes[index] @@ -403,13 +433,21 @@ RecallPanel = ClassUI(NinePatch.NinePatch) { if time > 0 then local dur = self.duration time = GetGameTimeSeconds() - time - local nominalWidth = self.Width() - LayoutHelpers.ScaleNumber(16) + local pb = self.progressBar + local bg = self.progressBarBG + local nominalWidth = bg.Width() - LayoutHelpers.ScaleNumber(16) if time >= dur then self.startTime:Set(-9999) - self.progressBar.Width:Set(0) - self.progressBar:Hide() + LayoutHelpers.AtHorizontalCenterIn(bg, pb) + pb.Width:Set(0) + pb:Hide() else - self.progressBar.Width:Set((1 - time / dur) * nominalWidth) + local wings = 0.5 * (1 - time / dur) * nominalWidth + -- it jitters less when you set both the left and the right instead of relying + -- on the layout centering with the width + local center = bg.Left() + 0.5 * bg.Width() + pb.Left:Set(math.floor(center - wings)) + pb.Right:Set(math.ceil(center + wings)) end notAnimating = false end From 76393d3ad0c1f00171cbad98914c389759cd989c Mon Sep 17 00:00:00 2001 From: BlackYps <52536103+BlackYps@users.noreply.github.com> Date: Sat, 16 Nov 2024 15:21:34 +0100 Subject: [PATCH 22/33] Shader rework (#6485) Now every shader is capable of using the new, better way of calculating the water absorption. The only requirement is that the light multiplier is bigger than 2.1. This is good, because the mesh shader doesn't know what terrain shader is in use and has worked like this before. This could lead to unfitting results, if the light multiplier was big enough, but a terrain shader was used that only supported the legacy water calculation. This is now fixed. I also made the decals able to use pbr light calculations. Now they behave consistently like the ground they are on. (Also the map editor does this automatically and we can't prevent it, so it helps the editor produce results that are consistent with the game.) Splats are unaffected for now. One thing about the decals is that they theoretically have a texture to define the specularity (or roughness), but this texture was black for all decals that I tested. I don't know if there are any decals that have this defined. --- changelog/snippets/other.6485.md | 2 + effects/mesh.fx | 4 +- effects/terrain.fx | 138 +++++++++++++++++-------------- 3 files changed, 81 insertions(+), 63 deletions(-) create mode 100644 changelog/snippets/other.6485.md diff --git a/changelog/snippets/other.6485.md b/changelog/snippets/other.6485.md new file mode 100644 index 0000000000..eeee1a325d --- /dev/null +++ b/changelog/snippets/other.6485.md @@ -0,0 +1,2 @@ +- (#6485) The new, better way of calculating the water absorption is now available for all terrain shaders. The only requirement is that the light multiplier is set to more than 2.1. Decals now use PBR light calculations if the terrain shader uses it, making them more consistent with the ground they are on. +- \ No newline at end of file diff --git a/effects/mesh.fx b/effects/mesh.fx index 22171ff685..c3c70c00a9 100644 --- a/effects/mesh.fx +++ b/effects/mesh.fx @@ -378,7 +378,7 @@ struct SHIELDIMPACT_VERTEX /// /////////////////////////////////////// -bool IsExperimentalShader() { +bool MapUsesAdvancedWater() { // lightMultiplier is one of the few variables that is driven by the map, // but accessible by the mesh shader. return lightMultiplier > 2.1; @@ -602,7 +602,7 @@ float3 ApplyWaterColor(float depth, float3 viewDirection, float3 color, float3 e // disable the whole thing on land-only maps if (surfaceElevation > 0) { // we need this switch to make it consistent with the terrain shader coloration - if (IsExperimentalShader()) { + if (MapUsesAdvancedWater()) { // We need to multiply by 2 to match the terrain shader. float scaledDepth = (-depth / (surfaceElevation - abyssElevation)) * 2; float3 up = float3(0,1,0); diff --git a/effects/terrain.fx b/effects/terrain.fx index 44f65662a3..e12b7f1893 100644 --- a/effects/terrain.fx +++ b/effects/terrain.fx @@ -344,16 +344,36 @@ VS_OUT FixedFuncVS( VS_IN In ) return Out; } -bool IsExperimentalShader() { +bool ShaderUsesTerrainInfoTexture() { // The tile value basically says how often the texture gets repeated on the map. // A value less than one doesn't make sense under normal conditions, so it is // relatively save to use it as our switch. + // We use the upper layer slot to store the terrain info texture, so we don't need + // the tile value for anything else. - // in order to trigger this you can set the albedo scale to be bigger than the map - // size. Use the value 10000 to be safe for any map + // In order to trigger this you need to set the albedo scale to be bigger than the + // map size in the editor. Use the value 10000 to be safe for any map return UpperAlbedoTile.x < 1.0; } +bool ShaderUsesPbrRendering() { + // The tile value basically says how often the texture gets repeated on the map. + // A value less than one doesn't make sense under normal conditions, so it is + // relatively save to use it as our switch. + // We use the stratum 7 normal slot to store the roughness texture, so we don't need + // the tile value for anything else. + + // In order to trigger this you need to set the normal scale to be bigger than the + // map size in the editor. Use the value 10000 to be safe for any map + return Stratum7NormalTile.x < 1.0; +} + +bool MapUsesAdvancedWater() { + // LightingMultiplier is one of the few variables that is driven by the map, + // but accessible by the mesh shader. + return LightingMultiplier > 2.1; +} + // sample a texture that is another buffer the same size as the one // we are rendering into and with the viewport setup the same way. float4 SampleScreen(sampler inSampler, float4 inTex) @@ -388,35 +408,29 @@ float ComputeShadow( float4 vShadowCoord ) return tex2D( ShadowSampler, vShadowCoord ).g; } -// apply the water color -float3 ApplyWaterColor(float terrainHeight, float waterDepth, float3 color) +float3 ApplyWaterColor(float3 viewDirection, float terrainHeight, float waterDepth, float3 color) { if (waterDepth > 0) { // With this extra check we get rid of unwanted coloration on steep cliffs when zoomed in, // but we prevent that terrain tesselation swallows too much of the water when zoomed out float opacity = saturate(smoothstep(10, 200, CameraPosition.y - WaterElevation) + step(terrainHeight, WaterElevation)); - float4 waterColor = tex1D(WaterRampSampler, waterDepth); - color = lerp(color.xyz, waterColor.rgb, waterColor.a * opacity); - } - return color; -} - -float3 ApplyWaterColorExponentially(float3 viewDirection, float terrainHeight, float waterDepth, float3 color) -{ - if (waterDepth > 0) { - float opacity = saturate(smoothstep(10, 200, CameraPosition.y - WaterElevation) + step(terrainHeight, WaterElevation)); - float3 up = float3(0,1,0); - // this is the length that the light travels underwater back to the camera - float oneOverCosV = 1 / max(dot(up, normalize(viewDirection)), 0.0001); - // Light gets absorbed exponentially, - // to simplify, we assume that the light enters vertically into the water. - // We need to multiply by 2 to reach 98% absorption as the waterDepth can't go over 1. - float waterAbsorption = 1 - saturate(exp(-waterDepth * 2 * (1 + oneOverCosV))); - // darken the color first to simulate the light absorption on the way in and out - color *= 1 - waterAbsorption * opacity; - // lerp in the watercolor to simulate the scattered light from the dirty water - float4 waterColor = tex1D(WaterRampSampler, waterAbsorption); - color = lerp(color, waterColor.rgb, waterAbsorption * opacity); + if (MapUsesAdvancedWater()) { + float3 up = float3(0,1,0); + // this is the length that the light travels underwater back to the camera + float oneOverCosV = 1 / max(dot(up, normalize(viewDirection)), 0.0001); + // Light gets absorbed exponentially, + // to simplify, we assume that the light enters vertically into the water. + // We need to multiply by 2 to reach 98% absorption as the waterDepth can't go over 1. + float waterAbsorption = 1 - saturate(exp(-waterDepth * 2 * (1 + oneOverCosV))); + // darken the color first to simulate the light absorption on the way in and out + color *= 1 - waterAbsorption * opacity; + // lerp in the watercolor to simulate the scattered light from the dirty water + float4 waterColor = tex1D(WaterRampSampler, waterAbsorption); + color = lerp(color, waterColor.rgb, waterAbsorption * opacity); + } else { + float4 waterColor = tex1D(WaterRampSampler, waterDepth); + color = lerp(color, waterColor.rgb, waterColor.a * opacity); + } } return color; } @@ -427,10 +441,10 @@ float4 CalculateLighting( float3 inNormal, float3 worldTerrain, float3 inAlbedo, float4 color = float4( 0, 0, 0, 0 ); float shadow = ( inShadows && ( 1 == ShadowsEnabled ) ) ? ComputeShadow(shadowCoords) : 1; - if (IsExperimentalShader()) { + if (ShaderUsesTerrainInfoTexture()) { float3 position = TerrainScale * worldTerrain; - float mapShadow = tex2D(UpperAlbedoSampler, position.xy).w; - shadow = shadow * mapShadow; + float terrainShadow = tex2D(UpperAlbedoSampler, position.xy).w; + shadow = shadow * terrainShadow; } // calculate some specular @@ -444,11 +458,7 @@ float4 CalculateLighting( float3 inNormal, float3 worldTerrain, float3 inAlbedo, light = LightingMultiplier * light + ShadowFillColor * ( 1 - light ); color.rgb = light * inAlbedo; - if (IsExperimentalShader()) { - color.rgb = ApplyWaterColorExponentially(-viewDirection, worldTerrain.z, waterDepth, color); - } else { - color.rgb = ApplyWaterColor(worldTerrain.z, waterDepth, color); - } + color.rgb = ApplyWaterColor(-viewDirection, worldTerrain.z, waterDepth, color); color.a = 0.01f + (specular*SpecularColor.w); return color; @@ -494,14 +504,14 @@ float GeometrySmith(float3 n, float nDotV, float3 l, float roughness) return gs1 * gs2; } -float3 PBR(VS_OUTPUT inV, float4 position, float3 albedo, float3 n, float roughness, float waterDepth) { +float3 PBR(VS_OUTPUT inV, float3 albedo, float3 n, float roughness, float waterDepth) { // See https://blog.selfshadow.com/publications/s2013-shading-course/ float shadow = 1; if (ShadowsEnabled == 1) { - float mapShadow = tex2D(UpperAlbedoSampler, position.xy).w; // 1 where sun is, 0 where shadow is + float terrainShadow = tex2D(UpperAlbedoSampler, TerrainScale * inV.mTexWT).w; // 1 where sun is, 0 where shadow is shadow = tex2D(ShadowSampler, inV.mShadow.xy).g; // 1 where sun is, 0 where shadow is - shadow *= mapShadow; + shadow *= terrainShadow; } float facingSpecular = 0.04; @@ -839,7 +849,7 @@ float4 TerrainBasisPS( VS_OUTPUT inV ) : COLOR float4 TerrainBasisPSBiCubic( VS_OUTPUT inV ) : COLOR { float4 result; - if (IsExperimentalShader()) { + if (ShaderUsesTerrainInfoTexture()) { float4 position = TerrainScale * inV.mTexWT; result = (float4(1, 1, tex2D(UpperAlbedoSampler, position.xy).xy)); } else { @@ -959,7 +969,7 @@ float4 TerrainAlbedoXP( VS_OUTPUT pixel) : COLOR albedo.rgb = light * ( albedo.rgb + specular.rgb ); float waterDepth = tex2D(UtilitySamplerC,pixel.mTexWT*TerrainScale).g; - albedo.rgb = ApplyWaterColor(pixel.mTexWT.z, waterDepth, albedo.rgb); + albedo.rgb = ApplyWaterColor(-pixel.mViewDirection, pixel.mTexWT.z, waterDepth, albedo.rgb); return float4(albedo.rgb, 0.01f); } @@ -1339,7 +1349,13 @@ float4 DecalsPS( VS_OUTPUT inV, uniform bool inShadows) : COLOR float waterDepth = tex2Dproj(UtilitySamplerC, inV.mTexWT * TerrainScale).g; - float3 color = CalculateLighting(normal, inV.mTexWT.xyz, decalAlbedo.xyz, decalSpec.r, waterDepth, inV.mShadow, inShadows).xyz; + float3 color; + // We want the decals to behave consistently with the rest of the ground + if (ShaderUsesPbrRendering()) { + color = PBR(inV, decalAlbedo.rgb, normal, 0.9 * (1-decalSpec.r), waterDepth); + } else { + color = CalculateLighting(normal, inV.mTexWT.xyz, decalAlbedo.xyz, decalSpec.r, waterDepth, inV.mShadow, inShadows).xyz; + } return float4(color.rgb, decalAlbedo.w * decalMask.w * DecalAlpha); } @@ -2021,11 +2037,11 @@ float4 TerrainPBRAlbedoPS ( VS_OUTPUT inV) : COLOR float roughness = saturate(albedo.a * mask1.w * 2 + 0.01); float waterDepth = tex2D(UpperAlbedoSampler, position.xy).b; - float3 color = PBR(inV, position, albedo, normal, roughness, waterDepth); - color = ApplyWaterColorExponentially(-1 * inV.mViewDirection, inV.mTexWT.z, waterDepth, color); + float3 color = PBR(inV, albedo, normal, roughness, waterDepth); + color = ApplyWaterColor(-1 * inV.mViewDirection, inV.mTexWT.z, waterDepth, color); return float4(color, 0.01f); - // SpecularColor.ba, LowerNormalTile, Stratum7AlbedoTile and Stratum7NormalTile are unused now + // SpecularColor.ba, LowerNormalTile and Stratum7AlbedoTile are unused now // Candidates for configurable values are the rotation matrix values } @@ -2169,14 +2185,14 @@ float4 Terrain001AlbedoPS ( VS_OUTPUT inV, uniform bool halfRange ) : COLOR // x = normals-x // y = normals-z - // z = unused + // z = water depth // w = shadows - float4 utility = tex2D(UpperAlbedoSampler, coordinates.xy); - float mapShadow = utility.w; + float4 terrainInfo = tex2D(UpperAlbedoSampler, coordinates.xy); + float terrainShadow = terrainInfo.w; // disable shadows when game settings tell us to if (0 == ShadowsEnabled) { - mapShadow = 1.0f; + terrainShadow = 1.0f; } // sample the albedo's @@ -2203,7 +2219,7 @@ float4 Terrain001AlbedoPS ( VS_OUTPUT inV, uniform bool halfRange ) : COLOR // compute the shadows, combining the baked and dynamic shadows float shadow = tex2D(ShadowSampler, inV.mShadow.xy).g; // 1 where sun is, 0 where shadow is - shadow = shadow * mapShadow; + shadow = shadow * terrainShadow; // normalize the pre-computed normal float3 normal = normalize(2 * SampleScreen(NormalSampler,inV.mTexSS).xyz - 1); @@ -2222,13 +2238,13 @@ float4 Terrain001AlbedoPS ( VS_OUTPUT inV, uniform bool halfRange ) : COLOR // compute water ramp intensity float waterDepth = tex2Dproj(UtilitySamplerC, coordinates).g; - albedo.rgb = ApplyWaterColorExponentially(-1 * inV.mViewDirection, inV.mTexWT.z, waterDepth, albedo.rgb); + albedo.rgb = ApplyWaterColor(-1 * inV.mViewDirection, inV.mTexWT.z, waterDepth, albedo.rgb); return float4(albedo.rgb, 0.01f); } /* # Similar to TTerrainXP, but upperAlbedo is used for map-wide # - # textures and we use better water color calculations. # + # textures. # # It is designed to be a drop-in replacement for TTerrainXP. # */ technique Terrain001 < string usage = "composite"; @@ -2395,14 +2411,14 @@ float4 Terrain003AlbedoPS ( VS_OUTPUT inV, uniform bool halfRange ) : COLOR // x = normals-x // y = normals-z - // z = unused + // z = water depth // w = shadows - float4 utility01 = tex2D(UpperAlbedoSampler, coordinates.xy); - float mapShadow = utility01.w; + float4 terrainInfo = tex2D(UpperAlbedoSampler, coordinates.xy); + float terrainShadow = terrainInfo.w; // disable shadows when game settings tell us to if (0 == ShadowsEnabled) { - mapShadow = 1.0f; + terrainShadow = 1.0f; } // x = specular @@ -2437,7 +2453,7 @@ float4 Terrain003AlbedoPS ( VS_OUTPUT inV, uniform bool halfRange ) : COLOR // compute the shadows, combining the baked and dynamic shadows float shadow = tex2D(ShadowSampler, inV.mShadow.xy).g; - shadow = shadow * mapShadow; + shadow = shadow * terrainShadow; // normalize the pre-computed normal float3 normal = normalize(2 * SampleScreen(NormalSampler,inV.mTexSS).xyz - 1); @@ -2456,7 +2472,7 @@ float4 Terrain003AlbedoPS ( VS_OUTPUT inV, uniform bool halfRange ) : COLOR // compute water ramp intensity float waterDepth = tex2D(UtilitySamplerC, coordinates).g; - albedo.rgb = ApplyWaterColorExponentially(-1 * inV.mViewDirection, inV.mTexWT.z, waterDepth, albedo.rgb); + albedo.rgb = ApplyWaterColor(-1 * inV.mViewDirection, inV.mTexWT.z, waterDepth, albedo.rgb); return float4(albedo.rgb, 0.01f); } @@ -2615,8 +2631,8 @@ float4 Terrain101AlbedoPS ( VS_OUTPUT inV, uniform bool halfRange ) : COLOR float roughness = saturate(albedo.a * mask1.w * 2 + 0.01); float waterDepth = tex2D(UpperAlbedoSampler, position.xy).b; - float3 color = PBR(inV, position, albedo, normal, roughness, waterDepth); - color = ApplyWaterColorExponentially(-1 * inV.mViewDirection, inV.mTexWT.z, waterDepth, color); + float3 color = PBR(inV, albedo, normal, roughness, waterDepth); + color = ApplyWaterColor(-1 * inV.mViewDirection, inV.mTexWT.z, waterDepth, color); return float4(color, 0.01f); } @@ -2800,8 +2816,8 @@ float4 Terrain301AlbedoPS ( VS_OUTPUT inV, uniform bool halfRange ) : COLOR float roughness = saturate(albedo.a * mask1.w * 2 + 0.01); float waterDepth = tex2D(UpperAlbedoSampler, position.xy).b; - float3 color = PBR(inV, position, albedo, normal, roughness, waterDepth); - color = ApplyWaterColorExponentially(-1 * inV.mViewDirection, inV.mTexWT.z, waterDepth, color); + float3 color = PBR(inV, albedo, normal, roughness, waterDepth); + color = ApplyWaterColor(-1 * inV.mViewDirection, inV.mTexWT.z, waterDepth, color); return float4(color, 0.01f); } From fd11eaef7cc6a810f43a8edb9e82f6c4c2cf1783 Mon Sep 17 00:00:00 2001 From: "(Jip) Willem Wijnia" Date: Sat, 16 Nov 2024 22:10:00 +0100 Subject: [PATCH 23/33] Conditionally define `table.empty` and `table.getsize` in Lua (#6537) --- lua/system/utils.lua | 48 ++++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/lua/system/utils.lua b/lua/system/utils.lua index 09bb5acbab..ff066e6c2f 100644 --- a/lua/system/utils.lua +++ b/lua/system/utils.lua @@ -56,29 +56,39 @@ function safecall(msg, fn, ...) end end ---- table.empty(t) returns true iff t has no keys/values. ----@param t table ----@return boolean -function table.empty(t) - if type(t) ~= 'table' then return true end - return next(t) == nil -end ---- Returns actual size of a table, including string keys ----@param t table ----@return number -function table.getsize(t) - if type(t) ~= 'table' then return 0 end - local size = 0 - for k, v in t do - size = size + 1 +if not rawget(table, 'empty') then + -- This function should be defined in the engine for performance. + -- See also: + -- - https://github.com/FAForever/FA-Binary-Patches/pull/98 + + --- table.empty(t) returns true iff t has no keys/values. + ---@param t table + ---@return boolean + function table.empty(t) + if type(t) ~= 'table' then return true end + return next(t) == nil end - return size end --- replace with assembly implementations -table.empty = table.empty2 or table.empty -table.getsize = table.getsize2 or table.getsize +if not rawget(table, 'getsize') then + -- This function should be defined in the engine for performance. + -- See also: + -- - https://github.com/FAForever/FA-Binary-Patches/pull/98 + + --- Returns actual size of a table, including string keys + ---@param t table + ---@return number + function table.getsize(t) + if type(t) ~= 'table' then return 0 end + local size = 0 + for k, v in t do + size = size + 1 + end + return size + end + +end --- Returns a shallow copy of t ---@generic T From 066354157c38c195e78f089d96b6288f2af473b1 Mon Sep 17 00:00:00 2001 From: "(Jip) Willem Wijnia" Date: Sun, 17 Nov 2024 16:08:15 +0100 Subject: [PATCH 24/33] Increase damage of Striker by 10 (#6540) ## Description of the proposed changes Closes #6539. ## Checklist - [ ] Changes are annotated, including comments where useful - [ ] Changes are documented in the changelog for the next game version --- units/UEL0201/UEL0201_unit.bp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/units/UEL0201/UEL0201_unit.bp b/units/UEL0201/UEL0201_unit.bp index 71f379b297..f8306800d0 100644 --- a/units/UEL0201/UEL0201_unit.bp +++ b/units/UEL0201/UEL0201_unit.bp @@ -143,7 +143,7 @@ UnitBlueprint{ }, BallisticArc = "RULEUBA_LowArc", CollideFriendly = false, - Damage = 24, + Damage = 34, DamageRadius = 0, DamageType = "Normal", DisplayName = "Gauss Cannon", From 1f08714ccc5a52ec5b9e0b61d3c4c1d1c1867054 Mon Sep 17 00:00:00 2001 From: "(Jip) Willem Wijnia" Date: Sun, 17 Nov 2024 16:20:52 +0100 Subject: [PATCH 25/33] Revert "Increase damage of Striker by 10" (#6542) --- units/UEL0201/UEL0201_unit.bp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/units/UEL0201/UEL0201_unit.bp b/units/UEL0201/UEL0201_unit.bp index f8306800d0..71f379b297 100644 --- a/units/UEL0201/UEL0201_unit.bp +++ b/units/UEL0201/UEL0201_unit.bp @@ -143,7 +143,7 @@ UnitBlueprint{ }, BallisticArc = "RULEUBA_LowArc", CollideFriendly = false, - Damage = 34, + Damage = 24, DamageRadius = 0, DamageType = "Normal", DisplayName = "Gauss Cannon", From aaeeec3726f88bace2b924b57a3f9fa0fa639e27 Mon Sep 17 00:00:00 2001 From: Basilisk3 <126026384+Basilisk3@users.noreply.github.com> Date: Sun, 17 Nov 2024 17:19:30 +0100 Subject: [PATCH 26/33] Readjust the Cybran ACU's Nanite Torpedo upgrade (#6476) ## Description of the proposed changes When the Nano-Repair upgrade was introduced to the Cybran ACU, the `MuzzleSalvoSize` of its torpedo upgrade was nerfed to prevent the combination from being too oppressive. Unfortunately, this change resulted in the torpedo upgrade being weak even against lower quantities of torpedo defenses since a single anti-torpedo projectile can negate 500 damage. To alleviate this issue, the torpedo upgrade now launches `3` instead of `2` torpedoes, but at a slightly lower DPS. Additionally, as with most other Cybran torpedo launchers, the damage is now dealt in multiple pulses. **Cybran Armored Command Unit (URL0001):** - Nanite Torpedo Launcher - Damage: 500 --> 60 (DoTPulses: 5) - MuzzleSalvoSize: 2 --> 3 - DPS: 250 --> 225 ## Checklist - [x] Changes are documented in the changelog for the next game version --- changelog/snippets/balance.6476.md | 7 +++++++ units/URL0001/URL0001_unit.bp | 6 ++++-- 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 changelog/snippets/balance.6476.md diff --git a/changelog/snippets/balance.6476.md b/changelog/snippets/balance.6476.md new file mode 100644 index 0000000000..a39142a9ba --- /dev/null +++ b/changelog/snippets/balance.6476.md @@ -0,0 +1,7 @@ +- (#6476) When the Nano-Repair upgrade was introduced to the Cybran ACU, the `MuzzleSalvoSize` of its torpedo upgrade was nerfed to prevent the combination from being too oppressive. Unfortunately, this change resulted in the torpedo upgrade being weak even against lower quantities of torpedo defenses, since a single anti-torpedo projectile can negate 500 damage. To alleviate this issue, the torpedo upgrade now launches `3` instead of `2` torpedoes, but at a slightly lower DPS. Additionally, as with most other Cybran torpedoes, their damage is now dealt in multiple pulses. + + - Cybran Armored Command Unit (URL0001): + - Nanite Torpedo Launcher + - Damage: 500 --> 60 (DoTPulses: 5) + - MuzzleSalvoSize: 2 --> 3 + - DPS: 250 --> 225 diff --git a/units/URL0001/URL0001_unit.bp b/units/URL0001/URL0001_unit.bp index a1432d9917..4a3b39e5dc 100644 --- a/units/URL0001/URL0001_unit.bp +++ b/units/URL0001/URL0001_unit.bp @@ -1026,9 +1026,11 @@ UnitBlueprint{ BallisticArc = "RULEUBA_None", BelowWaterFireOnly = true, CollideFriendly = false, - Damage = 500, + Damage = 60, DamageType = "Normal", DisplayName = "Nanite Torpedo", + DoTPulses = 5, + DoTTime = 0.4, EffectiveRadius = 0, EnabledByEnhancement = "NaniteTorpedoTube", FireTargetLayerCapsTable = { Seabed = "Seabed|Sub|Water" }, @@ -1036,7 +1038,7 @@ UnitBlueprint{ Label = "Torpedo", MaxRadius = 60, MuzzleSalvoDelay = 0.8, - MuzzleSalvoSize = 2, + MuzzleSalvoSize = 3, MuzzleVelocity = 5, ProjectileId = "/projectiles/CANTorpedoNanite01/CANTorpedoNanite01_proj.bp", ProjectileLifetime = 7, From 4ad2373bfbe6233c15511a768500f9c263d095cc Mon Sep 17 00:00:00 2001 From: Basilisk3 <126026384+Basilisk3@users.noreply.github.com> Date: Sun, 17 Nov 2024 22:06:49 +0100 Subject: [PATCH 27/33] Enable the SR's Iridium Rocket Pack to deal damage in an area of effect (#6383) ## Description of the proposed changes The SR has four air-to-ground weapons: two bolters, which are the more dominant weapons, and two rocket packs, which are often overlooked. In the current release, only the bolters have area of effect damage, the rocket packs have none at all. From a design perspective, this is suboptimal since the rockets have a decently large explosion effect. The Renegade uses the same projectile with similar effects and has an area of effect radius of 3. It is also not intuitive, because it could be reasonably assumed that the rocket packs perform similarly to either the Renegade or the other air-to-ground weapons of the SR. Balance wise, it results in the rocket packs underperforming against smaller targets, such as Titans. **Soul Ripper: Experimental Gunship (URA0401)**: - Iridium Rocket Pack (x2): - DamageRadius: 0 --> 3 - Damage: 190 --> 150 - DPS: 285 --> 225 ## Additional context Since this does increase the effectiveness of the rocket packs, their DPS is reduced. ## Checklist - [x] Changes are documented in the changelog for the next game version --- changelog/snippets/balance.6383.md | 7 +++++++ units/URA0401/URA0401_unit.bp | 6 ++++-- 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 changelog/snippets/balance.6383.md diff --git a/changelog/snippets/balance.6383.md b/changelog/snippets/balance.6383.md new file mode 100644 index 0000000000..ba3d8a1092 --- /dev/null +++ b/changelog/snippets/balance.6383.md @@ -0,0 +1,7 @@ +- (#6383) The Soul Ripper's Iridium Rocket Packs gain the same area of effect radius as its primary weapons. Visually, these weapons appeared like they dealt damage in an area of effect, however, this was never the case. Due to being very inaccurate, this caused them to underperform against smaller units. + + - Soul Ripper: Experimental Gunship (URA0401): + - Iridium Rocket Pack (x2): + - DamageRadius: 0 --> 3 + - Damage: 190 --> 150 + - DPS: 285 --> 225 diff --git a/units/URA0401/URA0401_unit.bp b/units/URA0401/URA0401_unit.bp index 59a386df63..152774a95c 100644 --- a/units/URA0401/URA0401_unit.bp +++ b/units/URA0401/URA0401_unit.bp @@ -229,7 +229,8 @@ UnitBlueprint{ AutoInitiateAttackCommand = true, BallisticArc = "RULEUBA_None", CollideFriendly = false, - Damage = 190, + Damage = 150, + DamageRadius = 3, DamageType = "Normal", DisplayName = "Iridium Rocket Pack", FireTargetLayerCapsTable = { @@ -293,7 +294,8 @@ UnitBlueprint{ AutoInitiateAttackCommand = true, BallisticArc = "RULEUBA_None", CollideFriendly = false, - Damage = 190, + Damage = 150, + DamageRadius = 3, DamageType = "Normal", DisplayName = "Iridium Rocket Pack", FireTargetLayerCapsTable = { From 64e1f66b341420cd6dc5b986562291240c9e5756 Mon Sep 17 00:00:00 2001 From: lL1l1 <82986251+lL1l1@users.noreply.github.com> Date: Mon, 18 Nov 2024 06:55:02 -0800 Subject: [PATCH 28/33] Use `Vector` types instead of tables for Scathis script (#6528) --- changelog/snippets/other.6438.md | 2 +- lua/EffectTemplates.lua | 2 +- units/URL0401/URL0401_Script.lua | 63 ++++++++++++++++++++++---------- 3 files changed, 45 insertions(+), 22 deletions(-) diff --git a/changelog/snippets/other.6438.md b/changelog/snippets/other.6438.md index d9e6646414..15ac7e4dc4 100644 --- a/changelog/snippets/other.6438.md +++ b/changelog/snippets/other.6438.md @@ -1,4 +1,4 @@ -- (#5061, #6438, #6527, #6525) Add metamethods and utility functions for Vectors and Quaternions to simplify and clean up the code involving operations with them. +- (#5061, #6438, #6527, #6525, #6528) Add metamethods and utility functions for Vectors and Quaternions to simplify and clean up the code involving operations with them. - This **removes** the file `/lua/shared/quaternions.lua`, which was added in #4768 (Mar 4, 2023), so mods that use that file will have to be updated. - The metamethods (defined globally in `/lua/system/utils.lua`) include: - Vector/Vector2 addition/subtraction/negation diff --git a/lua/EffectTemplates.lua b/lua/EffectTemplates.lua index ee90fb2593..436ae9c714 100644 --- a/lua/EffectTemplates.lua +++ b/lua/EffectTemplates.lua @@ -1456,7 +1456,7 @@ CArtilleryFlash01 = { EmtBpPath .. 'proton_artillery_muzzle_08_emit.bp', } CArtilleryFlash02 = { - EmtBpPath .. 'proton_artillery_muzzle_07_emit.bp', + EmtBpPath .. 'proton_artillery_muzzle_07_emit.bp', -- Large, faint rings of air expanding outwards } CArtilleryHit01 = DefaultHitExplosion01 diff --git a/units/URL0401/URL0401_Script.lua b/units/URL0401/URL0401_Script.lua index 2b089a40fd..ce3f60abfd 100644 --- a/units/URL0401/URL0401_Script.lua +++ b/units/URL0401/URL0401_Script.lua @@ -18,22 +18,39 @@ local muzzleBones = { 'Turret_Barrel_F_B03', 'Turret_Barrel_E_B03', 'Turret_Barr URL0401 = ClassUnit(CLandUnit) { Weapons = { + ---@class URL0401_Gun01 : CIFArtilleryWeapon + ---@field losttarget boolean + ---@field initialaim boolean + ---@field PitchRotators moho.RotateManipulator[] # Pitch rotators for the fake turret barrels + ---@field currentbarrel number # Which barrel is currently aligned with the aim's yaw + ---@field Goal number # Yaw goal of fake barrels + ---@field restdirvector Vector + ---@field dirvector Vector + ---@field basedirvector Vector + ---@field basediftorest number # "BaseDifToRest" angle in between Yaw aim bone and the resting fake barrel + ---@field pitchdif number # "PitchDif" angle in between fake barrel pitch and aim barrel pitch + ---@field Rotator moho.RotateManipulator # Yaw rotator for the `"Turret_Fake"` bone created every time the weapon fires after being packed + ---@field unit URL0401 Gun01 = ClassWeapon(CIFArtilleryWeapon) { - + ---@param self URL0401_Gun01 OnCreate = function(self) CIFArtilleryWeapon.OnCreate(self) self.losttarget = false self.initialaim = true self.PitchRotators = {} - self.restdirvector = {} + self.restdirvector = Vector(0, 0, 0) + self.dirvector = Vector(0, 0, 0) + self.basedirvector = Vector(0, 0, 0) self.currentbarrel = 1 end, + ---@param self URL0401_Gun01 OnLostTarget = function(self) CIFArtilleryWeapon.OnLostTarget(self) self.losttarget = true end, + ---@param self URL0401_Gun01 PlayFxWeaponPackSequence = function(self) if self.PitchRotators then for k, v in barrelBones do @@ -48,6 +65,7 @@ URL0401 = ClassUnit(CLandUnit) { CIFArtilleryWeapon.PlayFxWeaponPackSequence(self) end, + ---@param self URL0401_Gun01 LaunchEffects = function(self) local FxLaunch = EffectTemplate.CArtilleryFlash02 for k, v in FxLaunch do @@ -55,7 +73,17 @@ URL0401 = ClassUnit(CLandUnit) { end end, + --- Empty function because `CreateProjectileAtMuzzle` will wait when aiming the fake barrels, so the FX needs to be created in there for correct timing + ---@param self URL0401_Gun01 + ---@param muzzle Bone + PlayFxMuzzleSequence = function(self, muzzle) + end, + + ---@param self URL0401_Gun01 + ---@param muzzle Bone CreateProjectileAtMuzzle = function(self, muzzle) + -- set up the yaw and pitch rotators for the fake barrels since we just unpacked + -- Creates the animation where the barrels are at their lowest pitch and look spread out if self.initialaim then self.Rotator = CreateRotator(self.unit, 'Turret_Fake', 'y') self.unit.Trash:Add(self.Rotator) @@ -68,21 +96,23 @@ URL0401 = ClassUnit(CLandUnit) { self.unit.Trash:Add(self.PitchRotators[k]) end + -- fake barrel with the same yaw as the aim yaw local barrel = self.currentbarrel - local basedirvector = {} + local basedirvector = self.basedirvector self.Goal = 0 - self.restdirvector.x, self.restdirvector.y, self.restdirvector.z = self.unit:GetBoneDirection(barrelBones + self.restdirvector[1], self.restdirvector[2], self.restdirvector[3] = self.unit:GetBoneDirection(barrelBones [barrel]) - basedirvector.x, basedirvector.y, basedirvector.z = self.unit:GetBoneDirection('Turret_Aim') + basedirvector[1], basedirvector[2], basedirvector[3] = self.unit:GetBoneDirection('Turret_Aim') self.basediftorest = Util.GetAngleInBetween(self.restdirvector, basedirvector) end + -- since we got a new target, adjust the pitch of the fake barrels to match the aim barrel if self.losttarget or self.initialaim then - local dirvector = {} - dirvector.x, dirvector.y, dirvector.z = self.unit:GetBoneDirection('Turret_Aim_Barrel') - local basedirvector = {} - basedirvector.x, basedirvector.y, basedirvector.z = self.unit:GetBoneDirection('Turret_Aim') + local dirvector = self.dirvector + dirvector[1], dirvector[2], dirvector[3] = self.unit:GetBoneDirection('Turret_Aim_Barrel') + local basedirvector = self.basedirvector + basedirvector[1], basedirvector[2], basedirvector[3] = self.unit:GetBoneDirection('Turret_Aim') local basediftoaim = Util.GetAngleInBetween(dirvector, basedirvector) @@ -94,7 +124,7 @@ URL0401 = ClassUnit(CLandUnit) { end WaitFor(self.PitchRotators[1]) - + -- Wait for aesthetics, to let the barrel rest at the final position a bit before firing WaitTicks(3) if self.losttarget then @@ -106,19 +136,13 @@ URL0401 = ClassUnit(CLandUnit) { end end - local muzzleIdx = 0 - for i = 1, self.unit:GetBoneCount() do - if self.unit:GetBoneName(i) == 'Turret_Aim_Barrel_Muzzle' then - muzzleIdx = i - break - end - end - - CIFArtilleryWeapon.CreateProjectileAtMuzzle(self, muzzleIdx) + CIFArtilleryWeapon.PlayFxMuzzleSequence(self, muzzle) + CIFArtilleryWeapon.CreateProjectileAtMuzzle(self, muzzle) self.Trash:Add(ForkThread(self.LaunchEffects, self)) self.Trash:Add(ForkThread(self.RotateBarrels, self)) end, + ---@param self URL0401_Gun01 RotateBarrels = function(self) if not self.losttarget then self.Rotator:SetSpeed(320) @@ -132,7 +156,6 @@ URL0401 = ClassUnit(CLandUnit) { if self.currentbarrel > 6 then self.currentbarrel = 1 end - self.rotatedbarrel = true end end, }, From e2f24fbb4f01b116be05958b2f25662b0ffec6d8 Mon Sep 17 00:00:00 2001 From: Josh Date: Mon, 18 Nov 2024 17:02:07 +0000 Subject: [PATCH 29/33] Apply new enhancement processing convention to UEF SACU (#6515) --- .vscode/settings.json | 1 + changelog/snippets/other.6498.md | 2 +- units/UEL0301/UEL0301_script.lua | 278 ++++++++++++++++++++----------- 3 files changed, 187 insertions(+), 94 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 03e3e87a2a..8bdda3b316 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -54,6 +54,7 @@ "Athanah", "atleast", "attachedunit", + "attachee", "attackmove", "autobalance", "autoguns", diff --git a/changelog/snippets/other.6498.md b/changelog/snippets/other.6498.md index ff37651b8c..e1e3b6900c 100644 --- a/changelog/snippets/other.6498.md +++ b/changelog/snippets/other.6498.md @@ -1 +1 @@ -- (#6498, #6502, #6503) Refactor the Enhancements section in the ACU/SACU scripts to replace the long if/else chain with a more modular design that is easier to maintain and hook. Each enhancement has its own dedicated function, named with the format `ProcessEnhancement[EnhancementName]`. The CreateEnhancement function now calls the appropriate enhancement function automatically by that name format. +- (#6498, #6502, #6503, #6514) Refactor the Enhancements section in the ACU/SACU scripts to replace the long if/else chain with a more modular design that is easier to maintain and hook. Each enhancement has its own dedicated function, named with the format `ProcessEnhancement[EnhancementName]`. The CreateEnhancement function now calls the appropriate enhancement function automatically by that name format. \ No newline at end of file diff --git a/units/UEL0301/UEL0301_script.lua b/units/UEL0301/UEL0301_script.lua index 6a78aa6eed..83202766fa 100644 --- a/units/UEL0301/UEL0301_script.lua +++ b/units/UEL0301/UEL0301_script.lua @@ -5,12 +5,14 @@ -- Copyright © 2005 Gas Powered Games, Inc. All rights reserved. ----------------------------------------------------------------- -local Shield = import("/lua/shield.lua").Shield +local DefaultWep = import("/lua/sim/defaultweapons.lua") +local DefaultUnit = import("/lua/defaultunits.lua") local EffectUtil = import("/lua/effectutilities.lua") -local CommandUnit = import("/lua/defaultunits.lua").CommandUnit local TWeapons = import("/lua/terranweapons.lua") + +local CommandUnit = DefaultUnit.CommandUnit local TDFHeavyPlasmaCannonWeapon = TWeapons.TDFHeavyPlasmaCannonWeapon -local SCUDeathWeapon = import("/lua/sim/defaultweapons.lua").SCUDeathWeapon +local SCUDeathWeapon = DefaultWep.SCUDeathWeapon ---@class UEL0301 : CommandUnit UEL0301 = ClassUnit(CommandUnit) { @@ -54,7 +56,7 @@ UEL0301 = ClassUnit(CommandUnit) { ---@param self UEL0301 ---@param unitBeingBuilt Unit - ---@param order string + ---@param order string unused CreateBuildEffects = function(self, unitBeingBuilt, order) -- Different effect if we have building cube if unitBeingBuilt.BuildingCube then @@ -127,7 +129,7 @@ UEL0301 = ClassUnit(CommandUnit) { end, ---@param self UEL0301 - ---@param pod TConstructionPodUnit + ---@param pod TConstructionPodUnit unused ---@param rebuildDrone boolean NotifyOfPodDeath = function(self, pod, rebuildDrone) if rebuildDrone == true then @@ -171,101 +173,187 @@ UEL0301 = ClassUnit(CommandUnit) { attachee:SetDoNotTarget(false) end, + + -- ============================================================================================================================================================ + -- ENHANCEMENTS + + --- Drone Upgrade ---@param self UEL0301 - ---@param enh string - CreateEnhancement = function(self, enh) - CommandUnit.CreateEnhancement(self, enh) - local bp = self:GetBlueprint().Enhancements[enh] - if not bp then return end - if enh == 'Pod' then - local location = self:GetPosition('AttachSpecial01') - local pod = CreateUnitHPR('UEA0003', self.Army, location[1], location[2], location[3], 0, 0, 0) - pod:SetParent(self, 'Pod') - pod:SetCreator(self) - self.Trash:Add(pod) - self.HasPod = true - self.Pod = pod - elseif enh == 'PodRemove' then - if self.HasPod == true then - self.HasPod = false - if self.Pod and not self.Pod:BeenDestroyed() then - self.Pod:Kill() - self.Pod = nil - end - if self.RebuildingPod ~= nil then - RemoveEconomyEvent(self, self.RebuildingPod) - self.RebuildingPod = nil - end + ---@param bp UnitBlueprintEnhancement unused + ProcessEnhancementPod = function(self, bp) + local location = self:GetPosition('AttachSpecial01') + local pod = CreateUnitHPR('UEA0003', self.Army, location[1], location[2], location[3], 0, 0, 0) + pod:SetParent(self, 'Pod') + pod:SetCreator(self) + self.Trash:Add(pod) + self.HasPod = true + self.Pod = pod + end, + + ---@param self UEL0301 + ---@param bp UnitBlueprintEnhancement unused + ProcessEnhancementPodRemove = function(self, bp) + if self.HasPod == true then + self.HasPod = false + if self.Pod and not self.Pod:BeenDestroyed() then + self.Pod:Kill() + self.Pod = nil end - KillThread(self.RebuildThread) - elseif enh == 'Shield' then - self:AddToggleCap('RULEUTC_ShieldToggle') + if self.RebuildingPod ~= nil then + RemoveEconomyEvent(self, self.RebuildingPod) + self.RebuildingPod = nil + end + end + KillThread(self.RebuildThread) + end, + + ---@param self UEL0301 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementShield = function (self, bp) + self:AddToggleCap('RULEUTC_ShieldToggle') + self:SetEnergyMaintenanceConsumptionOverride(bp.MaintenanceConsumptionPerSecondEnergy or 0) + self:SetMaintenanceConsumptionActive() + self:CreateShield(bp) + end, + + ---@param self UEL0301 + ---@param bp UnitBlueprintEnhancement unused + ProcessEnhancementShieldRemove = function (self, bp) + RemoveUnitEnhancement(self, 'Shield') + self:DestroyShield() + self:SetMaintenanceConsumptionInactive() + self:RemoveToggleCap('RULEUTC_ShieldToggle') + end, + + ---@param self UEL0301 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementShieldGeneratorField = function(self, bp) + self:DestroyShield() + self:ForkThread(function() + WaitTicks(1) + self:CreateShield(bp) self:SetEnergyMaintenanceConsumptionOverride(bp.MaintenanceConsumptionPerSecondEnergy or 0) self:SetMaintenanceConsumptionActive() - self:CreateShield(bp) - elseif enh == 'ShieldRemove' then - RemoveUnitEnhancement(self, 'Shield') - self:DestroyShield() - self:SetMaintenanceConsumptionInactive() - self:RemoveToggleCap('RULEUTC_ShieldToggle') - elseif enh == 'ShieldGeneratorField' then - self:DestroyShield() - self:ForkThread(function() - WaitTicks(1) - self:CreateShield(bp) - self:SetEnergyMaintenanceConsumptionOverride(bp.MaintenanceConsumptionPerSecondEnergy or 0) - self:SetMaintenanceConsumptionActive() - end) - elseif enh == 'ShieldGeneratorFieldRemove' then - self:DestroyShield() - self:SetMaintenanceConsumptionInactive() - self:RemoveToggleCap('RULEUTC_ShieldToggle') - elseif enh =='ResourceAllocation' then - local bp = self:GetBlueprint().Enhancements[enh] - local bpEcon = self:GetBlueprint().Economy - if not bp then return end - self:SetProductionPerSecondEnergy((bp.ProductionPerSecondEnergy + bpEcon.ProductionPerSecondEnergy) or 0) - self:SetProductionPerSecondMass((bp.ProductionPerSecondMass + bpEcon.ProductionPerSecondMass) or 0) - elseif enh == 'ResourceAllocationRemove' then - local bpEcon = self:GetBlueprint().Economy - self:SetProductionPerSecondEnergy(bpEcon.ProductionPerSecondEnergy or 0) - self:SetProductionPerSecondMass(bpEcon.ProductionPerSecondMass or 0) - elseif enh == 'SensorRangeEnhancer' then - self:SetIntelRadius('Vision', bp.NewVisionRadius or 104) - self:SetIntelRadius('Omni', bp.NewOmniRadius or 104) - elseif enh == 'SensorRangeEnhancerRemove' then - local bpIntel = self:GetBlueprint().Intel - self:SetIntelRadius('Vision', bpIntel.VisionRadius or 26) - self:SetIntelRadius('Omni', bpIntel.OmniRadius or 26) - elseif enh == 'RadarJammer' then - self:SetIntelRadius('Jammer', bp.NewJammerRadius or 26) - self.RadarJammerEnh = true - self:EnableUnitIntel('Enhancement', 'Jammer') - self:AddToggleCap('RULEUTC_JammingToggle') - elseif enh == 'RadarJammerRemove' then - local bpIntel = self:GetBlueprint().Intel - self:SetIntelRadius('Jammer', 0) - self:DisableUnitIntel('Enhancement', 'Jammer') - self.RadarJammerEnh = false - self:RemoveToggleCap('RULEUTC_JammingToggle') - elseif enh =='AdvancedCoolingUpgrade' then - local wep = self:GetWeaponByLabel('RightHeavyPlasmaCannon') - wep:ChangeRateOfFire(bp.NewRateOfFire) - elseif enh =='AdvancedCoolingUpgradeRemove' then - local wep = self:GetWeaponByLabel('RightHeavyPlasmaCannon') - wep:ChangeRateOfFire(self:GetBlueprint().Weapon[1].RateOfFire or 1) - elseif enh =='HighExplosiveOrdnance' then - local wep = self:GetWeaponByLabel('RightHeavyPlasmaCannon') - wep:AddDamageRadiusMod(bp.NewDamageRadius) - wep:ChangeMaxRadius(bp.NewMaxRadius or 35) - elseif enh =='HighExplosiveOrdnanceRemove' then - local wep = self:GetWeaponByLabel('RightHeavyPlasmaCannon') - wep:AddDamageRadiusMod(bp.NewDamageRadius) - wep:ChangeMaxRadius(bp.NewMaxRadius or 25) + end) + end, + + ---@param self UEL0301 + ---@param bp UnitBlueprintEnhancement unused + ProcessEnhancementShieldGeneratorFieldRemove = function(self, bp) + self:DestroyShield() + self:SetMaintenanceConsumptionInactive() + self:RemoveToggleCap('RULEUTC_ShieldToggle') + end, + + ---@param self UEL0301 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementResourceAllocation = function(self, bp) + local bpEcon = self.Blueprint.Economy + self:SetProductionPerSecondEnergy((bp.ProductionPerSecondEnergy + bpEcon.ProductionPerSecondEnergy) or 0) + self:SetProductionPerSecondMass((bp.ProductionPerSecondMass + bpEcon.ProductionPerSecondMass) or 0) + end, + + ---@param self UEL0301 + ---@param bp UnitBlueprintEnhancement unused + ProcessEnhancementResourceAllocationRemove = function(self, bp) + local bpEcon = self.Blueprint.Economy + self:SetProductionPerSecondEnergy(bpEcon.ProductionPerSecondEnergy or 0) + self:SetProductionPerSecondMass(bpEcon.ProductionPerSecondMass or 0) + end, + + ---@param self UEL0301 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementSensorRangeEnhancer = function(self, bp) + self:SetIntelRadius('Vision', bp.NewVisionRadius or 104) + self:SetIntelRadius('Omni', bp.NewOmniRadius or 104) + end, + + ---@param self UEL0301 + ---@param bp UnitBlueprintEnhancement unused + ProcessEnhancementSensorRangeEnhancerRemove = function(self, bp) + local bpIntel = self.Blueprint.Intel + self:SetIntelRadius('Vision', bpIntel.VisionRadius or 26) + self:SetIntelRadius('Omni', bpIntel.OmniRadius or 26) + end, + + ---@param self UEL0301 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementRadarJammer = function(self, bp) + self:SetIntelRadius('Jammer', bp.NewJammerRadius or 26) + self:EnableUnitIntel('Enhancement', 'Jammer') + self:AddToggleCap('RULEUTC_JammingToggle') + self:SetEnergyMaintenanceConsumptionOverride(bp.MaintenanceConsumptionPerSecondEnergy or 0) + self:SetMaintenanceConsumptionActive() + + if self.IntelEffects then + self.IntelEffectsBag = {} + self:CreateTerrainTypeEffects(self.IntelEffects, 'FXIdle', self.Layer, nil, self.IntelEffectsBag) + end + + end, + + ---@param self UEL0301 + ---@param bp UnitBlueprintEnhancement unused + ProcessEnhancementRadarJammerRemove = function(self, bp) + self.RadarJammerEnh = false + self:SetIntelRadius('Jammer', 0) + self:DisableUnitIntel('Enhancement', 'Jammer') + self:RemoveToggleCap('RULEUTC_JammingToggle') + self:SetMaintenanceConsumptionInactive() + + if self.IntelEffectsBag then + EffectUtil.CleanupEffectBag(self, 'IntelEffectsBag') end + end, ---@param self UEL0301 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementAdvancedCoolingUpgrade = function(self, bp) + local wep = self:GetWeaponByLabel('RightHeavyPlasmaCannon') + wep:ChangeRateOfFire(bp.NewRateOfFire) + end, + + ---@param self UEL0301 + ---@param bp UnitBlueprintEnhancement unused + ProcessEnhancementAdvancedCoolingUpgradeRemove = function(self, bp) + local wep = self:GetWeaponByLabel('RightHeavyPlasmaCannon') + wep:ChangeRateOfFire(self.Blueprint.Weapon[1].RateOfFire or 1) + end, + + ---@param self UEL0301 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementHighExplosiveOrdnance = function(self, bp) + local wep = self:GetWeaponByLabel('RightHeavyPlasmaCannon') + wep:AddDamageRadiusMod(bp.NewDamageRadius) + wep:ChangeMaxRadius(bp.NewMaxRadius or 35) + end, + + ---@param self UEL0301 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementHighExplosiveOrdnanceRemove = function(self, bp) + local wep = self:GetWeaponByLabel('RightHeavyPlasmaCannon') + wep:AddDamageRadiusMod(bp.NewDamageRadius) + wep:ChangeMaxRadius(bp.NewMaxRadius or 25) + end, + + ---@param self UEL0301 + ---@param enh Enhancement + CreateEnhancement = function(self, enh) + CommandUnit.CreateEnhancement(self, enh) + local bp = self.Blueprint.Enhancements[enh] + if not bp then return end + + local ref = 'ProcessEnhancement' .. enh + local handler = self[ref] + if handler then + handler(self, bp) + else + WARN("Missing enhancement: ", enh, " for unit: ", self:GetUnitId(), " note that the function name should be called: ", ref) + end + end, + + ---@param self UEL0301 ---@param intel IntelType OnIntelEnabled = function(self, intel) CommandUnit.OnIntelEnabled(self, intel) @@ -274,7 +362,7 @@ UEL0301 = ClassUnit(CommandUnit) { self.IntelEffectsBag = {} self:CreateTerrainTypeEffects(self.IntelEffects, 'FXIdle', self.Layer, nil, self.IntelEffectsBag) end - self:SetEnergyMaintenanceConsumptionOverride(self:GetBlueprint().Enhancements['RadarJammer'].MaintenanceConsumptionPerSecondEnergy or 0) + self:SetEnergyMaintenanceConsumptionOverride(self.Blueprint.Enhancements['RadarJammer'].MaintenanceConsumptionPerSecondEnergy or 0) self:SetMaintenanceConsumptionActive() end end, @@ -293,3 +381,7 @@ UEL0301 = ClassUnit(CommandUnit) { } TypeClass = UEL0301 + +--#region Mod Compatibility +local Shield = import("/lua/shield.lua").Shield +--#endregion \ No newline at end of file From 53ecbd23932298096923cd5312f9ab9e24ba99c3 Mon Sep 17 00:00:00 2001 From: Josh Date: Mon, 18 Nov 2024 17:14:39 +0000 Subject: [PATCH 30/33] Apply new enhancement processing convention to Cybran SACU (#6517) --------- Co-authored-by: lL1l1 <82986251+lL1l1@users.noreply.github.com> --- changelog/snippets/other.6498.md | 2 +- units/URL0301/URL0301_script.lua | 357 +++++++++++++++++++------------ 2 files changed, 222 insertions(+), 137 deletions(-) diff --git a/changelog/snippets/other.6498.md b/changelog/snippets/other.6498.md index e1e3b6900c..65ecdb4f9b 100644 --- a/changelog/snippets/other.6498.md +++ b/changelog/snippets/other.6498.md @@ -1 +1 @@ -- (#6498, #6502, #6503, #6514) Refactor the Enhancements section in the ACU/SACU scripts to replace the long if/else chain with a more modular design that is easier to maintain and hook. Each enhancement has its own dedicated function, named with the format `ProcessEnhancement[EnhancementName]`. The CreateEnhancement function now calls the appropriate enhancement function automatically by that name format. \ No newline at end of file +- (#6498, #6502, #6503, #6514, #6517) Refactor the Enhancements section in the ACU/SACU scripts to replace the long if/else chain with a more modular design that is easier to maintain and hook. Each enhancement has its own dedicated function, named with the format `ProcessEnhancement[EnhancementName]`. The CreateEnhancement function now calls the appropriate enhancement function automatically by that name format. diff --git a/units/URL0301/URL0301_script.lua b/units/URL0301/URL0301_script.lua index 50723218c0..53fcd34a5b 100644 --- a/units/URL0301/URL0301_script.lua +++ b/units/URL0301/URL0301_script.lua @@ -26,6 +26,8 @@ local CDFLaserDisintegratorWeapon = CWeapons.CDFLaserDisintegratorWeapon02 local SCUDeathWeapon = import("/lua/sim/defaultweapons.lua").SCUDeathWeapon ---@class URL0301 : CCommandUnit +---@field HasStealthEnh? boolean +---@field HasCloakEnh? boolean URL0301 = ClassUnit(CCommandUnit) { LeftFoot = 'Left_Foot02', RightFoot = 'Right_Foot02', @@ -41,7 +43,7 @@ URL0301 = ClassUnit(CCommandUnit) { NMissile = ClassWeapon(CAAMissileNaniteWeapon) {}, }, - -- Creation + ---@param self URL0301 OnCreate = function(self) CCommandUnit.OnCreate(self) self:SetCapturable(false) @@ -56,10 +58,14 @@ URL0301 = ClassUnit(CCommandUnit) { end end, + ---@param self URL0301 __init = function(self) CCommandUnit.__init(self, 'RightDisintegrator') end, + ---@param self URL0301 + ---@param builder Unit + ---@param layer Layer OnStopBeingBuilt = function(self, builder, layer) CCommandUnit.OnStopBeingBuilt(self, builder, layer) self:BuildManipulatorSetEnabled(false) @@ -71,141 +77,216 @@ URL0301 = ClassUnit(CCommandUnit) { self.RightArmUpgrade = 'Disintegrator' end, - -- Enhancements + + -- =====================================================================================================================4 + -- ENHANCEMENTS + + ---@param self URL0301 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementCloakingGenerator = function (self, bp) + self:RemoveToggleCap('RULEUTC_StealthToggle') + self:AddToggleCap('RULEUTC_CloakToggle') + self.HasStealthEnh = false + self.HasCloakEnh = true + self:EnableUnitIntel('Enhancement', 'Cloak') + if not Buffs['CybranSCUCloakBonus'] then + BuffBlueprint { + Name = 'CybranSCUCloakBonus', + DisplayName = 'CybranSCUCloakBonus', + BuffType = 'SCUCLOAKBONUS', + Stacks = 'ALWAYS', + Duration = -1, + Affects = { + MaxHealth = { + Add = bp.NewHealth, + Mult = 1.0, + }, + }, + } + end + if Buff.HasBuff(self, 'CybranSCUCloakBonus') then + Buff.RemoveBuff(self, 'CybranSCUCloakBonus') + end + Buff.ApplyBuff(self, 'CybranSCUCloakBonus') + end, + + ---@param self URL0301 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementCloakingGeneratorRemove = function (self, bp) + -- remove prerequisites + self:RemoveToggleCap('RULEUTC_StealthToggle') + self:DisableUnitIntel('Enhancement', 'RadarStealth') + self:DisableUnitIntel('Enhancement', 'SonarStealth') + + -- remove cloak + self:DisableUnitIntel('Enhancement', 'Cloak') + self.HasCloakEnh = false + self:RemoveToggleCap('RULEUTC_CloakToggle') + if Buff.HasBuff(self, 'CybranSCUCloakBonus') then + Buff.RemoveBuff(self, 'CybranSCUCloakBonus') + end + end, + + ---@param self URL0301 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementStealthGenerator = function (self, bp) + self:AddToggleCap('RULEUTC_StealthToggle') + if self.IntelEffectsBag then + EffectUtil.CleanupEffectBag(self, 'IntelEffectsBag') + self.IntelEffectsBag = nil + end + self.HasStealthEnh = true + self:EnableUnitIntel('Enhancement', 'RadarStealth') + self:EnableUnitIntel('Enhancement', 'SonarStealth') + end, + + ---@param self URL0301 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementStealthGeneratorRemove = function (self, bp) + self:RemoveToggleCap('RULEUTC_StealthToggle') + self:DisableUnitIntel('Enhancement', 'RadarStealth') + self:DisableUnitIntel('Enhancement', 'SonarStealth') + self.HasStealthEnh = false + end, + + ---@param self URL0301 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementNaniteMissileSystem = function(self, bp) + self:ShowBone('AA_Gun', true) + self:SetWeaponEnabledByLabel('NMissile', true) + end, + + ---@param self URL0301 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementNaniteMissileSystemRemove = function(self, bp) + self:HideBone('AA_Gun', true) + self:SetWeaponEnabledByLabel('NMissile', false) + end, + + ---@param self URL0301 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementSelfRepairSystem = function(self, bp) + local bpRegenRate = self.Blueprint.Enhancements.SelfRepairSystem.NewRegenRate or 0 + if not Buffs['CybranSCURegenerateBonus'] then + BuffBlueprint { + Name = 'CybranSCURegenerateBonus', + DisplayName = 'CybranSCURegenerateBonus', + BuffType = 'SCUREGENERATEBONUS', + Stacks = 'ALWAYS', + Duration = -1, + Affects = { + Regen = { + Add = bpRegenRate, + Mult = 1.0, + }, + }, + } + end + if Buff.HasBuff(self, 'CybranSCURegenerateBonus') then + Buff.RemoveBuff(self, 'CybranSCURegenerateBonus') + end + Buff.ApplyBuff(self, 'CybranSCURegenerateBonus') + end, + + ---@param self URL0301 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementSelfRepairSystemRemove = function(self, bp) + if Buff.HasBuff(self, 'CybranSCURegenerateBonus') then + Buff.RemoveBuff(self, 'CybranSCURegenerateBonus') + end + end, + + ---@param self URL0301 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementResourceAllocation = function(self, bp) + local bpEcon = self.Blueprint.Economy + self:SetProductionPerSecondEnergy((bp.ProductionPerSecondEnergy + bpEcon.ProductionPerSecondEnergy) or 0) + self:SetProductionPerSecondMass((bp.ProductionPerSecondMass + bpEcon.ProductionPerSecondMass) or 0) + end, + + ---@param self URL0301 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementResourceAllocationRemove = function(self, bp) + local bpEcon = self.Blueprint.Economy + self:SetProductionPerSecondEnergy(bpEcon.ProductionPerSecondEnergy or 0) + self:SetProductionPerSecondMass(bpEcon.ProductionPerSecondMass or 0) + end, + + ---@param self URL0301 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementSwitchback = function(self, bp) + self.BuildBotTotal = 4 + if not Buffs['CybranSCUBuildRate'] then + BuffBlueprint { + Name = 'CybranSCUBuildRate', + DisplayName = 'CybranSCUBuildRate', + BuffType = 'SCUBUILDRATE', + Stacks = 'REPLACE', + Duration = -1, + Affects = { + BuildRate = { + Add = bp.NewBuildRate - self.Blueprint.Economy.BuildRate, + Mult = 1, + }, + }, + } + end + Buff.ApplyBuff(self, 'CybranSCUBuildRate') + end, + + ---@param self URL0301 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementSwitchbackRemove = function(self, bp) + self.BuildBotTotal = 3 + if Buff.HasBuff(self, 'CybranSCUBuildRate') then + Buff.RemoveBuff(self, 'CybranSCUBuildRate') + end + end, + + ---@param self URL0301 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementFocusConvertor = function(self, bp) + local wep = self:GetWeaponByLabel('RightDisintegrator') + wep:AddDamageMod(bp.NewDamageMod or 0) + wep:ChangeMaxRadius(bp.NewMaxRadius or 35) + end, + + ---@param self URL0301 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementFocusConvertorRemove = function(self, bp) + local wep = self:GetWeaponByLabel('RightDisintegrator') + wep:AddDamageMod(-self.Blueprint.Enhancements['FocusConvertor'].NewDamageMod) + wep:ChangeMaxRadius(self.Blueprint.Weapon[1].MaxRadius or 25) + end, + + ---@param self URL0301 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementEMPCharge = function(self, bp) + local wep = self:GetWeaponByLabel('RightDisintegrator') + wep:ReEnableBuff('STUN') + end, + + ---@param self URL0301 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementEMPChargeRemove = function(self, bp) + local wep = self:GetWeaponByLabel('RightDisintegrator') + wep:DisableBuff('STUN') + end, + + ---@param self URL0301 + ---@param enh Enhancement CreateEnhancement = function(self, enh) CCommandUnit.CreateEnhancement(self, enh) local bp = self.Blueprint.Enhancements[enh] if not bp then return end - if enh == 'CloakingGenerator' then - self:RemoveToggleCap('RULEUTC_StealthToggle') - self:AddToggleCap('RULEUTC_CloakToggle') - self.StealthEnh = false - self.CloakEnh = true - self:EnableUnitIntel('Enhancement', 'Cloak') - if not Buffs['CybranSCUCloakBonus'] then - BuffBlueprint { - Name = 'CybranSCUCloakBonus', - DisplayName = 'CybranSCUCloakBonus', - BuffType = 'SCUCLOAKBONUS', - Stacks = 'ALWAYS', - Duration = -1, - Affects = { - MaxHealth = { - Add = bp.NewHealth, - Mult = 1.0, - }, - }, - } - end - if Buff.HasBuff(self, 'CybranSCUCloakBonus') then - Buff.RemoveBuff(self, 'CybranSCUCloakBonus') - end - Buff.ApplyBuff(self, 'CybranSCUCloakBonus') - elseif enh == 'CloakingGeneratorRemove' then - -- remove prerequisites - self:RemoveToggleCap('RULEUTC_StealthToggle') - self:DisableUnitIntel('Enhancement', 'RadarStealth') - self:DisableUnitIntel('Enhancement', 'SonarStealth') - - -- remove cloak - self:DisableUnitIntel('Enhancement', 'Cloak') - self.CloakEnh = false - self:RemoveToggleCap('RULEUTC_CloakToggle') - if Buff.HasBuff(self, 'CybranSCUCloakBonus') then - Buff.RemoveBuff(self, 'CybranSCUCloakBonus') - end - elseif enh == 'StealthGenerator' then - self:AddToggleCap('RULEUTC_StealthToggle') - if self.IntelEffectsBag then - EffectUtil.CleanupEffectBag(self, 'IntelEffectsBag') - self.IntelEffectsBag = nil - end - self.StealthEnh = true - self:EnableUnitIntel('Enhancement', 'RadarStealth') - self:EnableUnitIntel('Enhancement', 'SonarStealth') - elseif enh == 'StealthGeneratorRemove' then - self:RemoveToggleCap('RULEUTC_StealthToggle') - self:DisableUnitIntel('Enhancement', 'RadarStealth') - self:DisableUnitIntel('Enhancement', 'SonarStealth') - self.StealthEnh = false - elseif enh == 'NaniteMissileSystem' then - self:ShowBone('AA_Gun', true) - self:SetWeaponEnabledByLabel('NMissile', true) - elseif enh == 'NaniteMissileSystemRemove' then - self:HideBone('AA_Gun', true) - self:SetWeaponEnabledByLabel('NMissile', false) - elseif enh == 'SelfRepairSystem' then - CCommandUnit.CreateEnhancement(self, enh) - local bpRegenRate = self.Blueprint.Enhancements.SelfRepairSystem.NewRegenRate or 0 - if not Buffs['CybranSCURegenerateBonus'] then - BuffBlueprint { - Name = 'CybranSCURegenerateBonus', - DisplayName = 'CybranSCURegenerateBonus', - BuffType = 'SCUREGENERATEBONUS', - Stacks = 'ALWAYS', - Duration = -1, - Affects = { - Regen = { - Add = bpRegenRate, - Mult = 1.0, - }, - }, - } - end - if Buff.HasBuff(self, 'CybranSCURegenerateBonus') then - Buff.RemoveBuff(self, 'CybranSCURegenerateBonus') - end - Buff.ApplyBuff(self, 'CybranSCURegenerateBonus') - elseif enh == 'SelfRepairSystemRemove' then - CCommandUnit.CreateEnhancement(self, enh) - if Buff.HasBuff(self, 'CybranSCURegenerateBonus') then - Buff.RemoveBuff(self, 'CybranSCURegenerateBonus') - end - elseif enh == 'ResourceAllocation' then - local bpEcon = self.Blueprint.Economy - self:SetProductionPerSecondEnergy((bp.ProductionPerSecondEnergy + bpEcon.ProductionPerSecondEnergy) or 0) - self:SetProductionPerSecondMass((bp.ProductionPerSecondMass + bpEcon.ProductionPerSecondMass) or 0) - elseif enh == 'ResourceAllocationRemove' then - local bpEcon = self.Blueprint.Economy - self:SetProductionPerSecondEnergy(bpEcon.ProductionPerSecondEnergy or 0) - self:SetProductionPerSecondMass(bpEcon.ProductionPerSecondMass or 0) - elseif enh == 'Switchback' then - self.BuildBotTotal = 4 - if not Buffs['CybranSCUBuildRate'] then - BuffBlueprint { - Name = 'CybranSCUBuildRate', - DisplayName = 'CybranSCUBuildRate', - BuffType = 'SCUBUILDRATE', - Stacks = 'REPLACE', - Duration = -1, - Affects = { - BuildRate = { - Add = bp.NewBuildRate - self.Blueprint.Economy.BuildRate, - Mult = 1, - }, - }, - } - end - Buff.ApplyBuff(self, 'CybranSCUBuildRate') - elseif enh == 'SwitchbackRemove' then - self.BuildBotTotal = 3 - if Buff.HasBuff(self, 'CybranSCUBuildRate') then - Buff.RemoveBuff(self, 'CybranSCUBuildRate') - end - elseif enh == 'FocusConvertor' then - local wep = self:GetWeaponByLabel('RightDisintegrator') - wep:AddDamageMod(bp.NewDamageMod or 0) - wep:ChangeMaxRadius(bp.NewMaxRadius or 35) - elseif enh == 'FocusConvertorRemove' then - local wep = self:GetWeaponByLabel('RightDisintegrator') - wep:AddDamageMod(-self.Blueprint.Enhancements['FocusConvertor'].NewDamageMod) - wep:ChangeMaxRadius(self.Blueprint.Weapon[1].MaxRadius or 25) - elseif enh == 'EMPCharge' then - local wep = self:GetWeaponByLabel('RightDisintegrator') - wep:ReEnableBuff('STUN') - elseif enh == 'EMPChargeRemove' then - local wep = self:GetWeaponByLabel('RightDisintegrator') - wep:DisableBuff('STUN') + + local ref = 'ProcessEnhancement' .. enh + local handler = self[ref] + + if handler then + handler(self, bp) + else + WARN("Missing enhancement: ", enh, " for unit: ", self:GetUnitId(), " note that the function name should be called: ", ref) end end, @@ -254,9 +335,11 @@ URL0301 = ClassUnit(CCommandUnit) { }, }, + ---@param self URL0301 + ---@param intel IntelType OnIntelEnabled = function(self, intel) CCommandUnit.OnIntelEnabled(self, intel) - if self.CloakEnh and self:IsIntelEnabled('Cloak') then + if self.HasCloakEnh and self:IsIntelEnabled('Cloak') then self:SetEnergyMaintenanceConsumptionOverride(self.Blueprint.Enhancements['CloakingGenerator'].MaintenanceConsumptionPerSecondEnergy or 0) self:SetMaintenanceConsumptionActive() @@ -264,7 +347,7 @@ URL0301 = ClassUnit(CCommandUnit) { self.IntelEffectsBag = {} self:CreateTerrainTypeEffects(self.IntelEffects.Cloak, 'FXIdle', self.Layer, nil, self.IntelEffectsBag) end - elseif self.StealthEnh and self:IsIntelEnabled('RadarStealth') and self:IsIntelEnabled('SonarStealth') then + elseif self.HasStealthEnh and self:IsIntelEnabled('RadarStealth') and self:IsIntelEnabled('SonarStealth') then self:SetEnergyMaintenanceConsumptionOverride(self.Blueprint.Enhancements['StealthGenerator'].MaintenanceConsumptionPerSecondEnergy or 0) self:SetMaintenanceConsumptionActive() @@ -275,15 +358,17 @@ URL0301 = ClassUnit(CCommandUnit) { end end, + ---@param self URL0301 + ---@param intel IntelType OnIntelDisabled = function(self, intel) CCommandUnit.OnIntelDisabled(self, intel) if self.IntelEffectsBag then EffectUtil.CleanupEffectBag(self, 'IntelEffectsBag') self.IntelEffectsBag = nil end - if self.CloakEnh and not self:IsIntelEnabled('Cloak') then + if self.HasCloakEnh and not self:IsIntelEnabled('Cloak') then self:SetMaintenanceConsumptionInactive() - elseif self.StealthEnh and not self:IsIntelEnabled('RadarStealth') and not self:IsIntelEnabled('SonarStealth') then + elseif self.HasStealthEnh and not self:IsIntelEnabled('RadarStealth') and not self:IsIntelEnabled('SonarStealth') then self:SetMaintenanceConsumptionInactive() end end, From d9e49e7352ef064b0ccc4f41f755ba952f005a29 Mon Sep 17 00:00:00 2001 From: Josh Date: Mon, 18 Nov 2024 17:25:39 +0000 Subject: [PATCH 31/33] Apply new enhancement processing convention to Seraphim SACU (#6518) --------- Co-authored-by: lL1l1 <82986251+lL1l1@users.noreply.github.com> --- changelog/snippets/other.6498.md | 2 +- units/XSL0301/XSL0301_script.lua | 285 +++++++++++++++++++------------ 2 files changed, 178 insertions(+), 109 deletions(-) diff --git a/changelog/snippets/other.6498.md b/changelog/snippets/other.6498.md index 65ecdb4f9b..0be67f4d48 100644 --- a/changelog/snippets/other.6498.md +++ b/changelog/snippets/other.6498.md @@ -1 +1 @@ -- (#6498, #6502, #6503, #6514, #6517) Refactor the Enhancements section in the ACU/SACU scripts to replace the long if/else chain with a more modular design that is easier to maintain and hook. Each enhancement has its own dedicated function, named with the format `ProcessEnhancement[EnhancementName]`. The CreateEnhancement function now calls the appropriate enhancement function automatically by that name format. +- (#6498, #6502, #6503, #6514, #6517, #6518) Refactor the Enhancements section in the ACU/SACU scripts to replace the long if/else chain with a more modular design that is easier to maintain and hook. Each enhancement has its own dedicated function, named with the format `ProcessEnhancement[EnhancementName]`. The CreateEnhancement function now calls the appropriate enhancement function automatically by that name format. diff --git a/units/XSL0301/XSL0301_script.lua b/units/XSL0301/XSL0301_script.lua index d848c1c289..d99402b11e 100644 --- a/units/XSL0301/XSL0301_script.lua +++ b/units/XSL0301/XSL0301_script.lua @@ -38,10 +38,12 @@ XSL0301 = ClassUnit(CommandUnit) { }, }, + ---@param self XSL0301 __init = function(self) CommandUnit.__init(self, 'LightChronatronCannon') end, + ---@param self XSL0301 OnCreate = function(self) CommandUnit.OnCreate(self) self:SetCapturable(false) @@ -51,126 +53,193 @@ XSL0301 = ClassUnit(CommandUnit) { self:GetWeaponByLabel('AutoOverCharge').NeedsUpgrade = true end, + ---@param self XSL0301 + ---@param builder Unit + ---@param layer Layer StartBeingBuiltEffects = function(self, builder, layer) CommandUnit.StartBeingBuiltEffects(self, builder, layer) self.Trash:Add(ForkThread(EffectUtil.CreateSeraphimBuildThread, self, builder, self.OnBeingBuiltEffectsBag, 2)) end, + ---@param self XSL0301 + ---@param unitBeingBuilt Unit + ---@param order string unused CreateBuildEffects = function(self, unitBeingBuilt, order) - EffectUtil.CreateSeraphimUnitEngineerBuildingEffects(self, unitBeingBuilt, self.BuildEffectBones, - self.BuildEffectsBag) + EffectUtil.CreateSeraphimUnitEngineerBuildingEffects(self, unitBeingBuilt, self.BuildEffectBones, self.BuildEffectsBag) end, + -- ===================================================================================================================== + -- EMHANCEMENTS + + ---@param self XSL0301 + ---@param bp UnitBlueprintEnhancement unused + ProcessEnhancementTeleporter = function(self, bp) + self:AddCommandCap('RULEUCC_Teleport') + end, + + ---@param self XSL0301 + ---@param bp UnitBlueprintEnhancement unused + ProcessEnhancementTeleporterRemove = function(self, bp) + self:RemoveCommandCap('RULEUCC_Teleport') + end, + + ---@param self XSL0301 + ---@param bp UnitBlueprintEnhancement unused + ProcessEnhancementMissile = function(self, bp) + self:AddCommandCap('RULEUCC_Tactical') + self:AddCommandCap('RULEUCC_SiloBuildTactical') + self:SetWeaponEnabledByLabel('Missile', true) + end, + + ---@param self XSL0301 + ---@param bp UnitBlueprintEnhancement unused + ProcessEnhancementMissileRemove = function(self, bp) + self:RemoveCommandCap('RULEUCC_Tactical') + self:RemoveCommandCap('RULEUCC_SiloBuildTactical') + self:SetWeaponEnabledByLabel('Missile', false) + end, + + ---@param self XSL0301 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementShield = function(self, bp) + self:AddToggleCap('RULEUTC_ShieldToggle') + self:SetEnergyMaintenanceConsumptionOverride(bp.MaintenanceConsumptionPerSecondEnergy or 0) + self:SetMaintenanceConsumptionActive() + self:CreateShield(bp) + end, + + ---@param self XSL0301 + ---@param bp UnitBlueprintEnhancement unused + ProcessEnhancementShieldRemove = function(self, bp) + self:DestroyShield() + self:SetMaintenanceConsumptionInactive() + self:RemoveToggleCap('RULEUTC_ShieldToggle') + end, + + ---@param self XSL0301 + ---@param bp UnitBlueprintEnhancement unused + ProcessEnhancementOvercharge = function(self, bp) + self:AddCommandCap('RULEUCC_Overcharge') + self:GetWeaponByLabel('OverCharge').NeedsUpgrade = false + self:GetWeaponByLabel('AutoOverCharge').NeedsUpgrade = false + end, + + ---@param self XSL0301 + ---@param bp UnitBlueprintEnhancement unused + ProcessEnhancementOverchargeRemove = function(self, bp) + self:RemoveCommandCap('RULEUCC_Overcharge') + self:SetWeaponEnabledByLabel('OverCharge', false) + self:SetWeaponEnabledByLabel('AutoOverCharge', false) + self:GetWeaponByLabel('OverCharge').NeedsUpgrade = true + self:GetWeaponByLabel('AutoOverCharge').NeedsUpgrade = true + end, + + ---@param self XSL0301 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementEngineeringThroughput = function(self, bp) + if not Buffs['SeraphimSCUBuildRate'] then + BuffBlueprint { + Name = 'SeraphimSCUBuildRate', + DisplayName = 'SeraphimSCUBuildRate', + BuffType = 'SCUBUILDRATE', + Stacks = 'REPLACE', + Duration = -1, + Affects = { + BuildRate = { + Add = bp.NewBuildRate - self.Blueprint.Economy.BuildRate, + Mult = 1, + }, + }, + } + end + Buff.ApplyBuff(self, 'SeraphimSCUBuildRate') + end, + + ---@param self XSL0301 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementEngineeringThroughputRemove = function(self, bp) + if Buff.HasBuff(self, 'SeraphimSCUBuildRate') then + Buff.RemoveBuff(self, 'SeraphimSCUBuildRate') + end + end, + + ---@param self XSL0301 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementDamageStabilization = function (self, bp) + if not Buffs['SeraphimSCUDamageStabilization'] then + BuffBlueprint { + Name = 'SeraphimSCUDamageStabilization', + DisplayName = 'SeraphimSCUDamageStabilization', + BuffType = 'SCUUPGRADEDMG', + Stacks = 'ALWAYS', + Duration = -1, + Affects = { + MaxHealth = { + Add = bp.NewHealth, + Mult = 1.0, + }, + Regen = { + Add = bp.NewRegenRate, + Mult = 1.0, + }, + }, + } + end + if Buff.HasBuff(self, 'SeraphimSCUDamageStabilization') then + Buff.RemoveBuff(self, 'SeraphimSCUDamageStabilization') + end + Buff.ApplyBuff(self, 'SeraphimSCUDamageStabilization') + end, + + ---@param self XSL0301 + ---@param bp UnitBlueprintEnhancement unused + ProcessEnhancementDamageStabilizationRemove = function (self, bp) + if Buff.HasBuff(self, 'SeraphimSCUDamageStabilization') then + Buff.RemoveBuff(self, 'SeraphimSCUDamageStabilization') + end + end, + + ---@param self XSL0301 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementEnhancedSensors = function(self, bp) + self:SetIntelRadius('Vision', bp.NewVisionRadius or 104) + self:SetIntelRadius('Omni', bp.NewOmniRadius or 104) + local wep = self:GetWeaponByLabel('LightChronatronCannon') + wep:ChangeMaxRadius(bp.NewMaxRadius or 35) + local wep = self:GetWeaponByLabel('OverCharge') + wep:ChangeMaxRadius(35) + local aoc = self:GetWeaponByLabel('AutoOverCharge') + aoc:ChangeMaxRadius(35) + end, + + ---@param self XSL0301 + ---@param bp UnitBlueprintEnhancement + ProcessEnhancementEnhancedSensorsRemove = function(self, bp) + local bpIntel = self.Blueprint.Intel + self:SetIntelRadius('Vision', bpIntel.VisionRadius or 26) + self:SetIntelRadius('Omni', bpIntel.OmniRadius or 16) + local wep = self:GetWeaponByLabel('LightChronatronCannon') + wep:ChangeMaxRadius(bp.NewMaxRadius or 25) + local wep = self:GetWeaponByLabel('OverCharge') + wep:ChangeMaxRadius(bp.NewMaxRadius or 25) + local aoc = self:GetWeaponByLabel('AutoOverCharge') + aoc:ChangeMaxRadius(bp.NewMaxRadius or 25) + end, + + ---@param self XSL0301 + ---@param enh Enhancement CreateEnhancement = function(self, enh) CommandUnit.CreateEnhancement(self, enh) local bp = self.Blueprint.Enhancements[enh] if not bp then return end - -- Teleporter - if enh == 'Teleporter' then - self:AddCommandCap('RULEUCC_Teleport') - elseif enh == 'TeleporterRemove' then - self:RemoveCommandCap('RULEUCC_Teleport') - -- Missile - elseif enh == 'Missile' then - self:AddCommandCap('RULEUCC_Tactical') - self:AddCommandCap('RULEUCC_SiloBuildTactical') - self:SetWeaponEnabledByLabel('Missile', true) - elseif enh == 'MissileRemove' then - self:RemoveCommandCap('RULEUCC_Tactical') - self:RemoveCommandCap('RULEUCC_SiloBuildTactical') - self:SetWeaponEnabledByLabel('Missile', false) - -- Shields - elseif enh == 'Shield' then - self:AddToggleCap('RULEUTC_ShieldToggle') - self:SetEnergyMaintenanceConsumptionOverride(bp.MaintenanceConsumptionPerSecondEnergy or 0) - self:SetMaintenanceConsumptionActive() - self:CreateShield(bp) - elseif enh == 'ShieldRemove' then - self:DestroyShield() - self:SetMaintenanceConsumptionInactive() - self:RemoveToggleCap('RULEUTC_ShieldToggle') - -- Overcharge - elseif enh == 'Overcharge' then - self:AddCommandCap('RULEUCC_Overcharge') - self:GetWeaponByLabel('OverCharge').NeedsUpgrade = false - self:GetWeaponByLabel('AutoOverCharge').NeedsUpgrade = false - elseif enh == 'OverchargeRemove' then - self:RemoveCommandCap('RULEUCC_Overcharge') - self:SetWeaponEnabledByLabel('OverCharge', false) - self:SetWeaponEnabledByLabel('AutoOverCharge', false) - self:GetWeaponByLabel('OverCharge').NeedsUpgrade = true - self:GetWeaponByLabel('AutoOverCharge').NeedsUpgrade = true - -- Engineering Throughput Upgrade - elseif enh == 'EngineeringThroughput' then - if not Buffs['SeraphimSCUBuildRate'] then - BuffBlueprint { - Name = 'SeraphimSCUBuildRate', - DisplayName = 'SeraphimSCUBuildRate', - BuffType = 'SCUBUILDRATE', - Stacks = 'REPLACE', - Duration = -1, - Affects = { - BuildRate = { - Add = bp.NewBuildRate - self.Blueprint.Economy.BuildRate, - Mult = 1, - }, - }, - } - end - Buff.ApplyBuff(self, 'SeraphimSCUBuildRate') - elseif enh == 'EngineeringThroughputRemove' then - if Buff.HasBuff(self, 'SeraphimSCUBuildRate') then - Buff.RemoveBuff(self, 'SeraphimSCUBuildRate') - end - -- Damage Stabilization - elseif enh == 'DamageStabilization' then - if not Buffs['SeraphimSCUDamageStabilization'] then - BuffBlueprint { - Name = 'SeraphimSCUDamageStabilization', - DisplayName = 'SeraphimSCUDamageStabilization', - BuffType = 'SCUUPGRADEDMG', - Stacks = 'ALWAYS', - Duration = -1, - Affects = { - MaxHealth = { - Add = bp.NewHealth, - Mult = 1.0, - }, - Regen = { - Add = bp.NewRegenRate, - Mult = 1.0, - }, - }, - } - end - if Buff.HasBuff(self, 'SeraphimSCUDamageStabilization') then - Buff.RemoveBuff(self, 'SeraphimSCUDamageStabilization') - end - Buff.ApplyBuff(self, 'SeraphimSCUDamageStabilization') - elseif enh == 'DamageStabilizationRemove' then - if Buff.HasBuff(self, 'SeraphimSCUDamageStabilization') then - Buff.RemoveBuff(self, 'SeraphimSCUDamageStabilization') - end - -- Enhanced Sensor Systems - elseif enh == 'EnhancedSensors' then - self:SetIntelRadius('Vision', bp.NewVisionRadius or 104) - self:SetIntelRadius('Omni', bp.NewOmniRadius or 104) - local wep = self:GetWeaponByLabel('LightChronatronCannon') - wep:ChangeMaxRadius(bp.NewMaxRadius or 35) - local wep = self:GetWeaponByLabel('OverCharge') - wep:ChangeMaxRadius(35) - local aoc = self:GetWeaponByLabel('AutoOverCharge') - aoc:ChangeMaxRadius(35) - elseif enh == 'EnhancedSensorsRemove' then - local bpIntel = self.Blueprint.Intel - self:SetIntelRadius('Vision', bpIntel.VisionRadius or 26) - self:SetIntelRadius('Omni', bpIntel.OmniRadius or 16) - local wep = self:GetWeaponByLabel('LightChronatronCannon') - wep:ChangeMaxRadius(bp.NewMaxRadius or 25) - local wep = self:GetWeaponByLabel('OverCharge') - wep:ChangeMaxRadius(bp.NewMaxRadius or 25) - local aoc = self:GetWeaponByLabel('AutoOverCharge') - aoc:ChangeMaxRadius(bp.NewMaxRadius or 25) + + local ref = 'ProcessEnhancement' .. enh + local handler = self[ref] + + if handler then + handler(self, bp) + else + WARN("Missing enhancement: ", enh, " for unit: ", self:GetUnitId(), " note that the function name should be called: ", ref) end end, } From 7e93c7c03dd942b1ea6e74d98ce1d28b652283a7 Mon Sep 17 00:00:00 2001 From: lL1l1 <82986251+lL1l1@users.noreply.github.com> Date: Tue, 19 Nov 2024 08:42:16 -0800 Subject: [PATCH 32/33] Fix three small issues with shields (#6445) - Fix overcharge dealing half damage to shields - Prevent tree effect damage from going through shields - Move anti-shield area damage before the normal area damage so that the normal damage can overkill things under the shield if the anti-shield damage kills the shield --- changelog/snippets/fix.6445.md | 5 +++++ lua/shield.lua | 18 ++++++++++------ lua/sim/Projectile.lua | 38 ++++++++++++++++++---------------- 3 files changed, 37 insertions(+), 24 deletions(-) create mode 100644 changelog/snippets/fix.6445.md diff --git a/changelog/snippets/fix.6445.md b/changelog/snippets/fix.6445.md new file mode 100644 index 0000000000..50eccb253d --- /dev/null +++ b/changelog/snippets/fix.6445.md @@ -0,0 +1,5 @@ +- (#6445) Fix overcharge dealing half damage to static and ACU shields. + +- (#6445) Shields now block damage from knocking down/setting fire to trees. + +- (#6445) Fix normal damage being applied before anti-shield damage, which didn't let the normal damage overkill under a shield. diff --git a/lua/shield.lua b/lua/shield.lua index 84fd60bc46..8a6d344f4f 100644 --- a/lua/shield.lua +++ b/lua/shield.lua @@ -461,7 +461,7 @@ Shield = ClassShield(moho.shield_methods, Entity) { ---@return number damageAbsorbed If not all damage is absorbed, the remainder passes to targets under the shield. OnGetDamageAbsorption = function(self, instigator, amount, type) if type == "TreeForce" or type == "TreeFire" then - return + return amount end -- Allow decoupling the shield from the owner's armor multiplier local absorptionMulti = self.AbsorptionTypeDamageTypeToMulti[type] or self.Owner:GetArmorMult(type) @@ -516,13 +516,19 @@ Shield = ClassShield(moho.shield_methods, Entity) { local tick = GetGameTick() -- damage correction for overcharge - -- These preset damages deal `2 * dmg * absorbMult or armorMult`, currently absorption multiplier is 1x so we need to divide by 2 + + -- If the absorption multiplier is less than 1, then the shield will get hit by two instances of area damage, the absorbed amount and the remainder. + -- This means that the following code then requires a multiplier to correct the total damage amount since both instances will be overriden. + -- For example with 0.25 absorption we would have a 0.25 damage and 0.75 damage instance hitting the shield. Both get set to 800 OC structure damage, + -- but then 0.25x armor is applied again (second OnGetDamageAbsorption call), reducing it to 2x200 damage, which requires a 2x multiplication to bring it to the expected 800. + -- Currently the absorption multiplier is 1, so we don't need a multiplier on the damage to balance it out. + if dmgType == 'Overcharge' then local wep = instigator:GetWeaponByLabel('OverCharge') - if self.StaticShield then -- fixed damage for static shields - amount = wep:GetBlueprint().Overcharge.structureDamage / 2 - elseif self.CommandShield then -- fixed damage for UEF bubble shield - amount = wep:GetBlueprint().Overcharge.commandDamage / 2 + if self.StaticShield then + amount = wep:GetBlueprint().Overcharge.structureDamage + elseif self.CommandShield then + amount = wep:GetBlueprint().Overcharge.commandDamage end end diff --git a/lua/sim/Projectile.lua b/lua/sim/Projectile.lua index 7db39fcf32..c6bd350623 100644 --- a/lua/sim/Projectile.lua +++ b/lua/sim/Projectile.lua @@ -637,16 +637,7 @@ Projectile = ClassProjectile(ProjectileMethods, DebugProjectileComponent) { local damageSelf = DamageData.DamageSelf or false -- do initial damage in a radius - DamageArea( - instigator, - cachedPosition, - radius, - damage + (DamageData.InitialDamageAmount or 0), - damageType, - damageFriendly, - damageSelf - ) - + -- anti-shield damage first so that the remaining damage can overkill under the shield local damageToShields = DamageData.DamageToShields if damageToShields then DamageArea( @@ -660,6 +651,16 @@ Projectile = ClassProjectile(ProjectileMethods, DebugProjectileComponent) { ) end + DamageArea( + instigator, + cachedPosition, + radius, + damage + (DamageData.InitialDamageAmount or 0), + damageType, + damageFriendly, + damageSelf + ) + -- check for and deal damage over time local DoTTime = DamageData.DoTTime if DoTTime > 0 then @@ -685,14 +686,7 @@ Projectile = ClassProjectile(ProjectileMethods, DebugProjectileComponent) { local damageType = DamageData.DamageType -- do initial damage - Damage( - instigator, - cachedPosition, - targetEntity, - damage + (DamageData.InitialDamageAmount or 0), - damageType - ) - + -- anti-shield damage first so remainder can overkill under the shield local damageToShields = DamageData.DamageToShields if damageToShields then Damage( @@ -704,6 +698,14 @@ Projectile = ClassProjectile(ProjectileMethods, DebugProjectileComponent) { ) end + Damage( + instigator, + cachedPosition, + targetEntity, + damage + (DamageData.InitialDamageAmount or 0), + damageType + ) + -- check for and apply damage over time local DoTTime = DamageData.DoTTime if DoTTime > 0 then From 1edef5535d659980f80f7cd5c40e56dc196a9308 Mon Sep 17 00:00:00 2001 From: lL1l1 <82986251+lL1l1@users.noreply.github.com> Date: Tue, 19 Nov 2024 08:42:53 -0800 Subject: [PATCH 33/33] Fix the printing of active mods for blueprint loading (#6426) --- changelog/snippets/fix.6426.md | 1 + lua/RuleInit.lua | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 changelog/snippets/fix.6426.md diff --git a/changelog/snippets/fix.6426.md b/changelog/snippets/fix.6426.md new file mode 100644 index 0000000000..10222f6d50 --- /dev/null +++ b/changelog/snippets/fix.6426.md @@ -0,0 +1 @@ +- (#6426) Fix the "Active game mods for blueprint loading" log message. diff --git a/lua/RuleInit.lua b/lua/RuleInit.lua index 7d87369242..9db7918960 100644 --- a/lua/RuleInit.lua +++ b/lua/RuleInit.lua @@ -13,7 +13,10 @@ doscript '/lua/system/utils.lua' doscript '/lua/system/repr.lua' doscript '/lua/system/debug.lua' -LOG('Active game mods for blueprint loading: ',repr(__active_mods)) +LOG('Active game mods for blueprint loading:') +for _, mod in __active_mods do + LOG(string.format('\t"%-30s v%02d (%-37s by %s', tostring(mod.name) .. '"', tostring(mod.version), tostring(mod.uid) .. ')', tostring(mod.author))) +end doscript '/lua/footprints.lua' doscript '/lua/system/Blueprints.lua'