From 8b92dbb5a609f9f8c3eda81bf46adccce0a25a82 Mon Sep 17 00:00:00 2001 From: UndeadZeratul Date: Mon, 25 May 2026 21:17:42 -0500 Subject: [PATCH 1/8] Initial Updates for Armour/Backpack Rework --- zscript/AI.zsc | 5 +- zscript/CommandMenu.zsc | 10 +- zscript/Data.zsc | 49 +++++----- zscript/Loadout.zsc | 30 +++--- zscript/Messages.zsc | 2 +- zscript/Recruitment.zsc | 3 +- zscript/Wearables.zsc | 212 +++++++++++++++++++--------------------- 7 files changed, 149 insertions(+), 162 deletions(-) diff --git a/zscript/AI.zsc b/zscript/AI.zsc index 35f0c31..2e8f6a6 100644 --- a/zscript/AI.zsc +++ b/zscript/AI.zsc @@ -69,12 +69,11 @@ extend class HDFollower SetStateLabel('StapleOwnAss'); } - if (Status <= FStatus_WaitingForWeapon) - //if (Status >= FStatus_WaitingForWearable && Status <= FStatus_WaitingForWeapon) + if (Status >= FStatus_WaitingForWearable && Status <= FStatus_WaitingForWeapon) { switch (Status) { - //case FStatus_WaitingForWearable: LookForWearable(); break; + case FStatus_WaitingForWearable: LookForWearable(); break; case FStatus_WaitingForWeapon: LookForWeapon(); break; } A_Face(LinkedPlayer, 15); diff --git a/zscript/CommandMenu.zsc b/zscript/CommandMenu.zsc index 0013f55..dda81c3 100644 --- a/zscript/CommandMenu.zsc +++ b/zscript/CommandMenu.zsc @@ -923,8 +923,9 @@ extend class FollowerHandler if (wearable is 'HDArmourWorn') { - sb.DrawImage(HDArmourWorn(wearable).mega?"ARMCA0":"ARMSA0", pos + size / 2, flags | sb.DI_ITEM_CENTER, box: size - (3, 3)); - sb.DrawString(fnt, (HDArmourWorn(wearable).Mega ? "\c[Blue]" : "\c[DarkGreen]")..HDArmourWorn(wearable).Durability, pos + size - (4, 10), flags | sb.DI_TEXT_ALIGN_RIGHT); + // TODO: Fix Translation being the player's + sb.DrawImage(""..HDArmourWorn(wearable).armoursprite, pos + size / 2, flags | sb.DI_ITEM_CENTER /* | sb.DI_TRANSLATABLE */, box: size - (3, 3)); + sb.DrawString(fnt, "\c[White]"..HDArmourWorn(wearable).durability, pos + size - (4, 10), flags | sb.DI_TEXT_ALIGN_RIGHT); } if (wearable is 'HDMagicShield') @@ -1798,9 +1799,8 @@ extend class FollowerHandler if (cmd.IndexOf("hdf_wearablecommand") != -1) { - - static const Name worldItemClasses[] = { 'HDArmour', 'PortableRadsuit', 'HDBackpack', 'HDPersonalShieldGenerator', 'ShieldCore' }; - static const Name wornItemClasses[] = { 'HDArmourWorn', 'WornRadsuit', 'HDBackpack', 'HDPersonalShieldGenerator', 'HDMagicShield' }; + static const Name worldItemClasses[] = { 'HDArmour', 'PortableRadsuit', 'HDStorageItem', 'HDPersonalShieldGenerator', 'ShieldCore' }; + static const Name wornItemClasses[] = { 'HDArmourWorn', 'WornRadsuit', 'HDStorageItem', 'HDPersonalShieldGenerator', 'HDMagicShield' }; static const string languagePrefixex[] = { "ARMORGIVE", "RADSUITGIVE", "BACKPACKGIVE", "PSGGIVE", "SHIELDCOREGIVE" }; static const string soundPrefixes[] = { "ArmorGive", "RadsuitGive", "BackpackGive", "PSGGive", "ShieldCoreGive" }; diff --git a/zscript/Data.zsc b/zscript/Data.zsc index b8f294b..c4c39c4 100644 --- a/zscript/Data.zsc +++ b/zscript/Data.zsc @@ -48,12 +48,14 @@ class FollowerData play int SuturesLeft; + name ArmorCls; int ArmorDurability; - bool ArmorMega; bool HasRadsuit; - ItemStorage Storage; + name StorageCls; + Array StorageContents; + int StorageBulk; Array WeaponData; Array ShieldData; @@ -141,19 +143,21 @@ extend class HDFollower data.SuturesLeft = SuturesLeft; - let armor = HDArmourWorn(FindInventory("HDArmourWorn")); + let armor = HDArmourWorn(FindInventory('HDArmourWorn', true)); if (armor) { - data.ArmorDurability = Armor.Durability; - data.ArmorMega = Armor.Mega; + data.ArmorCls = armor.GetClassName(); + data.ArmorDurability = armor.Durability; } data.HasRadsuit = FindInventory('WornRadsuit') != null; - let bpack = HDBackpack(FindInventory('HDBackpack')); + let bpack = HDStorageItem(FindInventory('HDStorageItem', true)); if (bpack) { - data.Storage = bpack.Storage; + data.StorageCls = bpack.GetClassName(); + data.StorageContents.copy(bpack.items); + data.StorageBulk = bpack.weaponBulk(); } for (Inventory next = Inv; next != null; next = Next.Inv) @@ -167,7 +171,7 @@ extend class HDFollower { wepData.WeaponStatus[i] = wpn.WeaponStatus[i]; } - Console.Printf(wpn.GetClassName()); + // Console.Printf(wpn.GetClassName()); data.WeaponData.Push(wepData); } @@ -225,7 +229,7 @@ class FollowerBody : HDPickup abstract double total = 0; for (int i = 0; i < Data.Size(); ++i) { - total += default.Bulk + (Data[i].Storage != null ? 100 + Data[i].Storage.TotalBulk * 0.7 : 0); + total += default.Bulk + Data[i].StorageBulk; } return total; } @@ -254,25 +258,19 @@ class FollowerBody : HDPickup abstract { FollowerData data = invoker.Data[invoker.Data.Size() - 1]; + bool forced = invoker.FromIncap || HDCore.isPreSpawn(); bool ReturnValue = false; for (int i = 0; i < 100; ++i) { Actor a; bool success; int Distance = random(20, 48); - int RandAngle = invoker.FromIncap || level.time < 2 ? random(0, 359) : 0; + int RandAngle = forced ? random(0, 359) : 0; vector3 dest = Vec3Angle(Distance, angle + RandAngle); - Sector sec = level.PointInSector(dest.xy); - if (sec && pos.z - sec.NextLowestFloorAt(dest.x, dest.y, dest.z) > GetDefaultByType(data.FollowerClass).MaxStepHeight || !level.IsPointInLevel(dest)) + Sector sec = Level.PointInSector(dest.xy); + if (sec && pos.z - sec.NextLowestFloorAt(dest.x, dest.y, dest.z) > GetDefaultByType(data.FollowerClass).MaxStepHeight || !Level.IsPointInLevel(dest)) { - if (invoker.FromIncap || level.time < 2) - { - continue; - } - else - { - break; - } + if (forced) continue; else break; } [success, a] = A_SpawnItemEx(data.FollowerClass, Distance, 0, random(8, 16), angle: RandAngle); @@ -320,9 +318,9 @@ class FollowerBody : HDPickup abstract if (data.ArmorDurability > 0) { - let armor = HDArmourWorn(fol.GiveInventoryType('HDArmourWorn')); + let armor = HDArmourWorn(fol.GiveInventoryType(data.ArmorCls)); armor.Durability = data.ArmorDurability; - armor.Mega = data.ArmorMega; + // armor.Mega = data.ArmorMega; } if (data.HasRadsuit) @@ -331,10 +329,11 @@ class FollowerBody : HDPickup abstract fol.GiveInventoryType('PortableRadsuit'); } - if (data.Storage != null) + if (data.StorageCls != 'None') { - let bpack = HDBackpack(fol.GiveInventoryType('HDBackpack')); - bpack.Storage = data.Storage; + let bpack = HDStorageItem(fol.GiveInventoryType(data.StorageCls)); + bpack.items.copy(data.StorageContents); + bpack.RecalculateBulk(); } for (int j = 0; j < data.WeaponData.Size(); ++j) diff --git a/zscript/Loadout.zsc b/zscript/Loadout.zsc index 03589eb..bd2baf6 100644 --- a/zscript/Loadout.zsc +++ b/zscript/Loadout.zsc @@ -14,25 +14,21 @@ extend class HDFollower for (int i = 0; i < lines.Size(); ++i) { - int amt = lines[i].Mid(3).ToInt(); - if (amt == 0) - { - amt = 1; - } - string ref = lines[i].Left(3); + int amt = max(1, lines[i].Mid(3).ToInt()); - class cls = HDBackpack.FindByRefId(ref); - if (!cls) - { - Continue; - } + class cls = HDHandlers.ParseRefID(lines[i].Left(3)); + if (!cls) Continue; - if (cls is 'GarrisonArmour' || cls is 'GarrisonArmourWorn' || cls is 'BattleArmour' || cls is 'BattleArmourWorn') + if (cls is 'HDArmour') { - bool mega = cls == 'BattleArmour' || cls == 'BattleArmourWorn'; - GiveArmour(1.0, mega ? 1.0 : 0, amt); + HDF.Give(self, cls); + UseInventory(FindInventory(cls)); continue; } + else if (cls is 'HDArmourWorn') + { + HDF.Give(self, cls); + } if (cls is 'HDWeapon' && !GetDefaultByType((class)(cls)).bWIMPY_WEAPON) { @@ -68,10 +64,10 @@ extend class HDFollower } else { - if (cls is 'HDBackpack') + if (cls is 'HDStorageItem') { - let bp = HDBackpack(GiveInventoryType('HDBackpack')); - bp.LoadoutConfigure(input.Mid(input.IndexOf("bak") + 3)); + let bp = HDStorageItem(GiveInventoryType(cls)); + bp.LoadoutConfigure(input.Mid(input.IndexOf(lines[i].Left(3)) + 3)); } else if (cls is 'PortableRadsuit') { diff --git a/zscript/Messages.zsc b/zscript/Messages.zsc index 6b502f5..e1323b3 100644 --- a/zscript/Messages.zsc +++ b/zscript/Messages.zsc @@ -41,7 +41,7 @@ extend class HDFollower private void ResetCommentTimer() { - CommentTimer = random(35 * 15, 35 * 50); + CommentTimer = random(TICRATE * 15, TICRATE * 50); } protected void PlayIdleComment() diff --git a/zscript/Recruitment.zsc b/zscript/Recruitment.zsc index e5bcbfa..41bbc6c 100644 --- a/zscript/Recruitment.zsc +++ b/zscript/Recruitment.zsc @@ -117,9 +117,8 @@ extend class FollowerHandler let Armor = HDArmourWorn(m.FindInventory('HDArmourWorn')); if (Armor) { - let NewArmor = HDArmourWorn(recruit.GiveInventoryType('HDArmourWorn')); + let NewArmor = HDArmourWorn(recruit.GiveInventoryType(Armor.GetClassName())); NewArmor.Durability = Armor.Durability; - NewArmor.Mega = Armor.Mega; } if (m.InStateSequence(m.CurState, m.FindState('Falldown'))) diff --git a/zscript/Wearables.zsc b/zscript/Wearables.zsc index b15d190..8c9587c 100644 --- a/zscript/Wearables.zsc +++ b/zscript/Wearables.zsc @@ -36,9 +36,12 @@ extend class HDFollower return; } - CheckProximity(WearableInfo.worldItemCls, radius + MaxSearchRange, 1, CPXF_CLOSEST | CPXF_SETTRACER); + CheckProximity(WearableInfo.worldItemCls, radius + MaxSearchRange, 1, CPXF_ANCESTOR|CPXF_CLOSEST|CPXF_SETTRACER); let foundWearable = Inventory(tracer); - let wornWearable = FindInventory(WearableInfo.WornItemCls); + let wornWearable = FindInventory(WearableInfo.WornItemCls, true); + + let hw = HDWeapon(foundWearable); + let hp = HDPickup(foundWearable); if (foundWearable) { @@ -46,17 +49,16 @@ extend class HDFollower { PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_NONE"), WearableInfo.SoundPrefix.."/None", PMType_General); } - else if (WearableInfo.GetWornItem() is 'HDArmourWorn') + else if (wornWearable is 'HDArmourWorn') { let foundArmor = HDArmour(foundWearable); let wornArmor = HDArmourWorn(wornWearable); // [Ace] 1030 because mega armor's durability is calculated as 1000 + actual durability. Only while on the ground, though. - if (!wornArmor.Mega && foundArmor.Mega && foundArmor.Mags[0] >= 1020 || wornArmor.Mega && wornArmor.Durability < 20 && !foundArmor.Mega && foundArmor.Mags[0] > 60) - { - PrintMessage(GetResponse("ARMORGIVE_UPGRADE"), "ArmorGive/Upgrade", PMType_General); - } - else if (foundArmor.Mega == wornArmor.Mega && wornArmor.Durability < foundArmor.Mags[0] % 1000) + // [UZ] 2026-04-21: Armour Refactor removed "mega" property, now split into individual actors + // TODO: Generify Armor Preference/Priority + // For Now, simply replace via scaled durability + if (((1.0 * wornArmor.Durability) / wornArmor.default.Durability) < ((1.0 * foundArmor.Mags[foundArmor.Mags.Size() - 1]) / foundArmor.maxperunit)) { PrintMessage(GetResponse("ARMORGIVE_BETTERDURABILITY"), "ArmorGive/BetterDurability", PMType_General); } @@ -76,16 +78,15 @@ extend class HDFollower return; } - //while (HDPlayerPawn.CheckStrip(self, foundWearable) != CSResult_Nothing); - while (HDPlayerPawn.CheckStrip(self, foundWearable)); + if ((hp && hp.wornlayer > 0) || (hw && hw.wornlayer > 0)) while (!HDPlayerPawn.CheckStrip(self, foundWearable)); - if (HDWeapon(foundWearable)) + if (hw) { - HDWeapon(foundWearable).ActualPickup(self); + hw.ActualPickup(self); } - else if (HDPickup(foundWearable)) + else if (hp) { - HDPickup(foundWearable).ActualPickup(self); + hp.ActualPickup(self); UseInventory(FindInventory(foundWearable.GetClass())); } Status = FStatus_None; @@ -95,127 +96,120 @@ extend class HDFollower void TryUseWearable() { - if (Status == FStatus_InactiveOrDead || !WearableInfo.GetWorldItem() || !WearableInfo.GetWornItem()) - { - return; - } - - if (Status == FStatus_WaitingForWearable) - { - CheckProximity('HDArmour', radius + 24, 1, CPXF_CLOSEST | CPXF_SETTRACER); - let FoundArmor = HDArmour(tracer); - let WornArmor = HDArmourWorn(FindInventory('HDArmourWorn')); - CheckProximity('HDBackpack', radius + 24, 1, CPXF_CLOSEST | CPXF_SETTRACER); - let FoundBackpack = HDBackpack(tracer); - let WornBackpack = HDBackpack(FindInventory('HDBackpack')); + if (!WearableInfo.GetWorldItem() || !WearableInfo.GetWornItem()) return; - CheckProximity('Shieldcore', radius + 24, 1, CPXF_CLOSEST | CPXF_SETTRACER); - let FoundShield = Shieldcore(tracer); - let WornShield = HDMagicShield(FindInventory('HDMagicShield')); + switch (Status) { + case FStatus_InactiveOrDead: + // no-op + break; + case FStatus_WaitingForWearable: + + CheckProximity('HDArmour', radius + 24, 1, CPXF_ANCESTOR|CPXF_CLOSEST|CPXF_SETTRACER); + let FoundArmor = HDArmour(tracer); + let WornArmor = HDArmourWorn(FindInventory('HDArmourWorn', true)); - if (FoundArmor) - { - string ATag = GetTag(); - if (WornArmor) + CheckProximity('HDStorageItem', radius + 24, 1, CPXF_ANCESTOR|CPXF_CLOSEST|CPXF_SETTRACER); + let FoundBackpack = HDStorageItem(tracer); + let WornBackpack = HDStorageItem(FindInventory('HDStorageItem', true)); + + CheckProximity('Shieldcore', radius + 24, 1, CPXF_CLOSEST|CPXF_SETTRACER); + let FoundShield = Shieldcore(tracer); + let WornShield = HDMagicShield(FindInventory('HDMagicShield')); + + if (FoundArmor) { - // [Ace] 1030 because mega armor's durability is calculated as 1000 + actual durability. Only while on the ground, though. - if (!WornArmor.Mega && FoundArmor.Mega && FoundArmor.Mags[0] >= 1030 || WornArmor.Mega && WornArmor.Durability < 20 && !FoundArmor.Mega && FoundArmor.Mags[0] > 60) + if (WornArmor) { - PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_UPGRADE"), WearableInfo.SoundPrefix.."/Upgrade", PMType_General); + // [Ace] 1030 because mega armor's durability is calculated as 1000 + actual durability. Only while on the ground, though. + // [UZ] 2026-04-21: Armour Refactor removed "mega" property, now split into individual actors + // TODO: Generify Armor Preference/Priority + // For Now, simply replace via scaled durability + if (((1.0 * WornArmor.Durability) / WornArmor.default.Durability) < ((1.0 * FoundArmor.Mags[FoundArmor.Mags.Size() - 1]) / FoundArmor.maxperunit)) + { + PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_BETTERDURABILITY"), WearableInfo.SoundPrefix.."/BetterDurability", PMType_General); + + A_DropInventory(WornArmor.GetClass()); + } + else + { + PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_NOTHANKS"), WearableInfo.SoundPrefix.."/NoThanks", PMType_Important); + Status = FStatus_None; + tracer = null; + return; + } } - else if (FoundArmor.Mega == WornArmor.Mega && WornArmor.Durability < FoundArmor.Mags[0] % 1000) + else { - PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_BETTERDURABILITY"), WearableInfo.SoundPrefix.."/BetterDurability", PMType_General); + PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_NONE"), WearableInfo.SoundPrefix.."/None", PMType_General); } - else + + + FoundArmor.ActualPickup(self); + if (FoundArmor.bDROPTRANSLATION) FoundArmor.translation = translation; + UseInventory(FoundArmor); + Status = FStatus_None; + return; + } + else if (FoundBackpack) + { + if (WornBackpack) { - PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_NOTHANKS"), WearableInfo.SoundPrefix.."/NoThanks", PMType_Important); + PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_CANCEL"), WearableInfo.SoundPrefix.."/Cancel", PMType_Important); Status = FStatus_None; tracer = null; return; } - - A_DropInventory(WornArmor.GetClass()); - } - else - { - PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_NONE"), WearableInfo.SoundPrefix.."/None", PMType_General); - } + else + { + PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_NONE"), WearableInfo.SoundPrefix.."/None", PMType_General); + } - FoundArmor.ActualPickup(self); - UseInventory(FindInventory("HDArmour")); - Status = FStatus_None; - return; - } - else if (FoundBackpack) - { - string BTag = GetTag(); - if (WornBackpack) - { - PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_CANCEL"), WearableInfo.SoundPrefix.."/Cancel", PMType_Important); + FoundBackpack.ActualPickup(self); + Status = FStatus_None; + return; } - else + else if (FoundShield) { - PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_NONE"), WearableInfo.SoundPrefix.."/None", PMType_General); - } - - FoundBackpack.ActualPickup(self); - Status = FStatus_None; - return; - } - else if (FoundShield) - { - string STag = GetTag(); - if (WornShield) - { - if (FoundShield.amount > WornShield.amount) + if (WornShield) { - PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_BETTERDURABILITY"), WearableInfo.SoundPrefix.."/BetterDurability", PMType_General); - - A_DropInventory(WornShield.GetClass()); + if (FoundShield.amount > WornShield.amount) + { + PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_BETTERDURABILITY"), WearableInfo.SoundPrefix.."/BetterDurability", PMType_General); + + A_DropInventory(WornShield.GetClass()); + } + else + { + PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_NOTHANKS"), WearableInfo.SoundPrefix.."/NoThanks", PMType_Important); + Status = FStatus_None; + tracer = null; + return; + } } else { - PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_NOTHANKS"), WearableInfo.SoundPrefix.."/NoThanks", PMType_Important); - Status = FStatus_None; - tracer = null; - return; + PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_NONE"), WearableInfo.SoundPrefix.."/None", PMType_General); } + + FoundShield.ActualPickup(self); + UseInventory(FoundShield); + Status = FStatus_None; + return; } else { - PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_NONE"), WearableInfo.SoundPrefix.."/None", PMType_General); - } - - A_GiveInventory("HDMagicShield"); - let sss = HDPickup(FindInventory("HDMagicShield")); - if (sss) - { - sss.amount = 1; - sss.maxamount = FoundShield.mags[FoundShield.mags.size() - 1]; - sss.bulk = FoundShield.magBulk; - sss.mass = FoundShield.amount - 1; - - if (sss.amount > 0) HDMagicShield.FlashSparks(self); + PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_CANCEL"), WearableInfo.SoundPrefix.."/Cancel", PMType_Important); } - FoundShield.destroy(); + tracer = null; Status = FStatus_None; - return; - } - else - { - PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_CANCEL"), WearableInfo.SoundPrefix.."/Cancel", PMType_Important); - } - - tracer = null; - Status = FStatus_None; - return; + break; + default: + Status = FStatus_WaitingForWearable; + PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_INIT"), WearableInfo.SoundPrefix.."/Init", PMType_Important); + break; } - - Status = FStatus_WaitingForWearable; - PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_INIT"), WearableInfo.SoundPrefix.."/Init", PMType_Important); } void DropWearable() @@ -225,10 +219,10 @@ extend class HDFollower return; } - let wornItem = FindInventory(WearableInfo.WornItemCls); + let wornItem = FindInventory(WearableInfo.WornItemCls, true); if (wornItem) { - A_DropInventory(WearableInfo.WornItemCls); + A_DropInventory(wornItem.GetClass()); PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_STRIP"), WearableInfo.SoundPrefix.."/Strip", PMType_General); Status = FStatus_None; } From a841d9c5e6996d1828e370b11233b143300914b6 Mon Sep 17 00:00:00 2001 From: UndeadZeratul Date: Mon, 25 May 2026 21:17:53 -0500 Subject: [PATCH 2/8] Add safety checks --- zscript/FollowerHandler.zsc | 2 +- zscript/Management.zsc | 21 ++++++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/zscript/FollowerHandler.zsc b/zscript/FollowerHandler.zsc index 942a33e..27cc139 100644 --- a/zscript/FollowerHandler.zsc +++ b/zscript/FollowerHandler.zsc @@ -88,7 +88,7 @@ class FollowerHandler : EventHandler while (Followers.Size() > 0) { HDFollower flw = Followers[0]; - if (flw.IsDeadOrMissing()) + if (!flw || flw.IsDeadOrMissing()) { RemoveFollower(flw, true); } diff --git a/zscript/Management.zsc b/zscript/Management.zsc index 86b52f6..53bb89c 100644 --- a/zscript/Management.zsc +++ b/zscript/Management.zsc @@ -65,18 +65,21 @@ extend class FollowerHandler void RemoveFollower(HDFollower flw, bool permanent = false, bool noDestroy = false) { - int Index = Followers.Find(flw); - if (Index != Followers.Size()) + if (flw) { - if (permanent && flw.MissingClass && players[Net_Arbitrator].mo) + int Index = Followers.Find(flw); + if (Index != Followers.Size()) { - players[Net_Arbitrator].mo.A_GiveInventory(flw.MissingClass); - } - if (!noDestroy) - { - flw.Destroy(); + if (permanent && flw.MissingClass && players[Net_Arbitrator].mo) + { + players[Net_Arbitrator].mo.A_GiveInventory(flw.MissingClass); + } + if (!noDestroy) + { + flw.Destroy(); + } + Followers.Delete(Index); } - Followers.Delete(Index); } } From 8497a2f08c0a397dd5b9beea2d7fac708c4a5200 Mon Sep 17 00:00:00 2001 From: UndeadZeratul Date: Mon, 25 May 2026 21:18:45 -0500 Subject: [PATCH 3/8] Initial Commit to begin adopting HDCoreLib Currently mainly using the logging framework, will integrate further later --- README.md | 3 ++- zscript/Medical.zsc | 30 ++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4c4738d..5725f53 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # Hideous Destructor Followers _Originally made by Accensus, now maintained by the community._ +_Requires [HDCoreLib](https://github.com/HDest-Community/hdest-core-lib)._ **NOTE: *Freylis's sprites are not for public use. Please do not use them anywhere. Thank you.*** @@ -131,4 +132,4 @@ Sounds: > ### Known Issues > --- > - Sometimes followers will fail to follow the player or other followers while in specific sectors even if nothing obvious is blocking their way. This primarily happens on older maps that use the REJECT lump for sight checking. I don't know of a possible fix without side effects that I could implement on the mod's side. -> \ No newline at end of file +> diff --git a/zscript/Medical.zsc b/zscript/Medical.zsc index 56c9130..e3638e6 100644 --- a/zscript/Medical.zsc +++ b/zscript/Medical.zsc @@ -14,6 +14,8 @@ extend class HDFollower protected void StopHealing() { + HDCore.log("Follower."..getClassName(), LOGGING_DEBUG, "Stopping Healing..."); + ClearGoal(); Status = FStatus_None; Order = PrevOrder; @@ -22,16 +24,24 @@ extend class HDFollower protected void HealSelf(int amt, int flags = HSF_ALL) { + HDCore.log("Follower."..getClassName(), LOGGING_DEBUG, "Healing Self..."); + if (flags & HSF_HEALTH) { + HDCore.log("Follower."..getClassName(), LOGGING_DEBUG, "Giving Health."); + GiveBody(amt); } if (flags & HSF_BODYDAMAGE) { + HDCore.log("Follower."..getClassName(), LOGGING_DEBUG, "Removing Body Damage."); + BodyDamage = max(0, BodyDamage - amt); } if (flags & HSF_BLOOD) { + HDCore.log("Follower."..getClassName(), LOGGING_DEBUG, "Refilling Blood."); + Bloodloss = max(0, Bloodloss - amt); } } @@ -75,6 +85,8 @@ extend class HDFollower protected bool TryUnwrapMedikit() { + HDCore.log("Follower."..getClassName(), LOGGING_DEBUG, "Attempting to unwrap Medikit..."); + if (GetAmount('PortableMedikit') > 0) { // Give/Drop Stimpack @@ -201,6 +213,8 @@ extend class HDFollower if (LinkedPlayer.incaptimer > MinIncap) { + HDCore.log("Follower."..getClassName(), LOGGING_DEBUG, "Helping up player..."); + A_Face(LinkedPlayer); if (!random(0, 4)) { @@ -214,6 +228,8 @@ extend class HDFollower #### JK 5; #### L 5 { + HDCore.log("Follower."..getClassName(), LOGGING_DEBUG, "Assessing H. Dumpty..."); + // If player is too far or is ordered to stop, stop. if (DistanceToPlayer > HDCONST_ONEMETRE * 1.2 || Order != FOrder_ComeAndHelp) { @@ -269,6 +285,8 @@ extend class HDFollower } #### LLLLL 3 { + HDCore.log("Follower."..getClassName(), LOGGING_DEBUG, "Assessing H. Dumpty, part 2..."); + if (DistanceToPlayer > HDCONST_ONEMETRE * 1.2 || Order != FOrder_ComeAndHelp) { StopHealing(); @@ -290,6 +308,8 @@ extend class HDFollower PatchUpEnd: #### J 10 { + HDCore.log("Follower."..getClassName(), LOGGING_DEBUG, "Assessing H. Dumpty, part 3..."); + SuturesLeft--; PatchPlayerWounds(1); } @@ -299,6 +319,8 @@ extend class HDFollower #### A 10; #### A 5 { + HDCore.log("Follower."..getClassName(), LOGGING_DEBUG, "Assessing self..."); + if (!CanHealSelf() || Order != FOrder_HealSelf) { StopHealing(); @@ -319,6 +341,8 @@ extend class HDFollower } #### GGGGG 3 { + HDCore.log("Follower."..getClassName(), LOGGING_DEBUG, "Assessing self, part 2..."); + if (Order != FOrder_HealSelf) { StopHealing(); @@ -332,6 +356,8 @@ extend class HDFollower PatchUpSelfEnd: #### A 10 { + HDCore.log("Follower."..getClassName(), LOGGING_DEBUG, "Assessing self, part 3..."); + HealSelf(10, HSF_HEALTH | HSF_BODYDAMAGE); SuturesLeft--; } @@ -341,6 +367,7 @@ extend class HDFollower #### J 25 A_Face(LinkedPlayer); #### J 20 { + HDCore.log("Follower."..getClassName(), LOGGING_DEBUG, "Apply second blood..."); // if(patient)invoker.weaponstatus[SBS_INJECTCOUNTER]++;else{ // invoker.weaponstatus[SBS_INJECTCOUNTER]=0; // return; @@ -360,6 +387,8 @@ extend class HDFollower #### J 25 A_Face(LinkedPlayer); #### J 20 { + HDCore.log("Follower."..getClassName(), LOGGING_DEBUG, "Apply stim..."); + A_Face(LinkedPlayer); LinkedPlayer.A_SetBlend("7a 3a 18", 0.1, 4); LinkedPlayer.A_SetPitch(pitch + 2, SPF_INTERPOLATE); @@ -381,6 +410,7 @@ extend class HDFollower #### J 25 A_Face(LinkedPlayer); #### J 20 { + HDCore.log("Follower."..getClassName(), LOGGING_DEBUG, "Apply second flesh..."); PatchPlayerBurns(1); } From 9e985f9f95a206ef10f40e40a7422f04aff7b79a Mon Sep 17 00:00:00 2001 From: UndeadZeratul Date: Fri, 5 Jun 2026 18:57:03 -0500 Subject: [PATCH 4/8] Translate the Wearable Icon if it normally would when dropped. --- zscript/CommandMenu.zsc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/zscript/CommandMenu.zsc b/zscript/CommandMenu.zsc index dda81c3..861d2c9 100644 --- a/zscript/CommandMenu.zsc +++ b/zscript/CommandMenu.zsc @@ -923,8 +923,11 @@ extend class FollowerHandler if (wearable is 'HDArmourWorn') { - // TODO: Fix Translation being the player's - sb.DrawImage(""..HDArmourWorn(wearable).armoursprite, pos + size / 2, flags | sb.DI_ITEM_CENTER /* | sb.DI_TRANSLATABLE */, box: size - (3, 3)); + if (wearable.bDROPTRANSLATION) { + sb.DrawImage(""..HDArmourWorn(wearable).armoursprite, pos + size / 2, flags | sb.DI_ITEM_CENTER | sb.DI_TRANSLATABLE, box: size - (3, 3), translation: wearable.owner.translation); + } else { + sb.DrawImage(""..HDArmourWorn(wearable).armoursprite, pos + size / 2, flags | sb.DI_ITEM_CENTER, box: size - (3, 3)); + } sb.DrawString(fnt, "\c[White]"..HDArmourWorn(wearable).durability, pos + size - (4, 10), flags | sb.DI_TEXT_ALIGN_RIGHT); } From d885fbb9ab0504d4e6afffb0613550691cf5a07f Mon Sep 17 00:00:00 2001 From: UndeadZeratul Date: Fri, 5 Jun 2026 18:57:54 -0500 Subject: [PATCH 5/8] Minor Formatting --- zscript/CommandMenu.zsc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/zscript/CommandMenu.zsc b/zscript/CommandMenu.zsc index 861d2c9..6ec1051 100644 --- a/zscript/CommandMenu.zsc +++ b/zscript/CommandMenu.zsc @@ -1802,10 +1802,10 @@ extend class FollowerHandler if (cmd.IndexOf("hdf_wearablecommand") != -1) { - static const Name worldItemClasses[] = { 'HDArmour', 'PortableRadsuit', 'HDStorageItem', 'HDPersonalShieldGenerator', 'ShieldCore' }; - static const Name wornItemClasses[] = { 'HDArmourWorn', 'WornRadsuit', 'HDStorageItem', 'HDPersonalShieldGenerator', 'HDMagicShield' }; - static const string languagePrefixex[] = { "ARMORGIVE", "RADSUITGIVE", "BACKPACKGIVE", "PSGGIVE", "SHIELDCOREGIVE" }; - static const string soundPrefixes[] = { "ArmorGive", "RadsuitGive", "BackpackGive", "PSGGive", "ShieldCoreGive" }; + static const Name worldItemClasses[] = { 'HDArmour', 'PortableRadsuit', 'HDStorageItem', 'HDPersonalShieldGenerator', 'ShieldCore' }; + static const Name wornItemClasses[] = { 'HDArmourWorn', 'WornRadsuit', 'HDStorageItem', 'HDPersonalShieldGenerator', 'HDMagicShield' }; + static const string languagePrefixex[] = { "ARMORGIVE", "RADSUITGIVE", "BACKPACKGIVE", "PSGGIVE", "SHIELDCOREGIVE" }; + static const string soundPrefixes[] = { "ArmorGive", "RadsuitGive", "BackpackGive", "PSGGive", "ShieldCoreGive" }; lastFol.WearableInfo.WorldItemCls = worldItemClasses[e.Args[0]]; lastFol.WearableInfo.WornItemCls = wornItemClasses[e.Args[0]]; From cdeeea9f5aa380d487a25e37b703cc54d96c7b23 Mon Sep 17 00:00:00 2001 From: UndeadZeratul Date: Fri, 5 Jun 2026 18:58:14 -0500 Subject: [PATCH 6/8] Fix shield durability calculation --- zscript/Wearables.zsc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zscript/Wearables.zsc b/zscript/Wearables.zsc index 8c9587c..9045aa8 100644 --- a/zscript/Wearables.zsc +++ b/zscript/Wearables.zsc @@ -173,7 +173,7 @@ extend class HDFollower { if (WornShield) { - if (FoundShield.amount > WornShield.amount) + if (FoundShield.mags[FoundShield.mags.size() - 1] > WornShield.amount) { PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_BETTERDURABILITY"), WearableInfo.SoundPrefix.."/BetterDurability", PMType_General); From 989201b2bcc6f4a7a733f978e3735a9cb3a087ea Mon Sep 17 00:00:00 2001 From: UndeadZeratul Date: Fri, 5 Jun 2026 18:58:24 -0500 Subject: [PATCH 7/8] Minor Class Name casing --- zscript/Wearables.zsc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zscript/Wearables.zsc b/zscript/Wearables.zsc index 9045aa8..1fa4dde 100644 --- a/zscript/Wearables.zsc +++ b/zscript/Wearables.zsc @@ -113,8 +113,8 @@ extend class HDFollower let FoundBackpack = HDStorageItem(tracer); let WornBackpack = HDStorageItem(FindInventory('HDStorageItem', true)); - CheckProximity('Shieldcore', radius + 24, 1, CPXF_CLOSEST|CPXF_SETTRACER); - let FoundShield = Shieldcore(tracer); + CheckProximity('ShieldCore', radius + 24, 1, CPXF_CLOSEST|CPXF_SETTRACER); + let FoundShield = ShieldCore(tracer); let WornShield = HDMagicShield(FindInventory('HDMagicShield')); if (FoundArmor) From 9f3174069159f38ddfcfe1de1e306a79f1ad76d0 Mon Sep 17 00:00:00 2001 From: UndeadZeratul Date: Fri, 5 Jun 2026 22:00:34 -0500 Subject: [PATCH 8/8] Refine Wearables Logic Followers can wear multiple layers of wearables, addon armors, helmets, storage items, etc. Reaction Messages added for new "wearing over existing wearables" state. Cleaned up seemingly duplicated logic between TryUseWearable() & LookForWearable() Added more HDCoreLib Logging --- LANGUAGE.Balthazar | 4 + LANGUAGE.Daina | 4 + LANGUAGE.Freylis | 4 + LANGUAGE.Nari | 4 + LANGUAGE.RecruitedMarine | 4 + LANGUAGE.Sewie | 4 + zscript/CommandMenu.zsc | 30 ++++- zscript/Wearables.zsc | 252 +++++++++++++++++++++------------------ 8 files changed, 184 insertions(+), 122 deletions(-) diff --git a/LANGUAGE.Balthazar b/LANGUAGE.Balthazar index 9430f5f..ce4c3be 100644 --- a/LANGUAGE.Balthazar +++ b/LANGUAGE.Balthazar @@ -146,6 +146,10 @@ BALTHAZAR_ARMORGIVE_UPGRADE_1 = "WE LOVE UPGRADES!"; BALTHAZAR_ARMORGIVE_UPGRADE_2 = "BETTER IS GOOD!"; BALTHAZAR_ARMORGIVE_UPGRADE_3 = "STRONG!"; +BALTHAZAR_ARMORGIVE_WORNOVER_1 = "ANOTHER ONE!"; +BALTHAZAR_ARMORGIVE_WORNOVER_2 = "MORE BETTER!"; +BALTHAZAR_ARMORGIVE_WORNOVER_3 = "EVEN MORE!"; + BALTHAZAR_ARMORGIVE_BETTERDURABILITY_1 = "MUCH BETTER!"; BALTHAZAR_ARMORGIVE_BETTERDURABILITY_2 = "WE LIKE THAT!"; BALTHAZAR_ARMORGIVE_BETTERDURABILITY_3 = "AH YES!"; diff --git a/LANGUAGE.Daina b/LANGUAGE.Daina index 8349636..caa13de 100644 --- a/LANGUAGE.Daina +++ b/LANGUAGE.Daina @@ -218,6 +218,10 @@ DAINA_ARMORGIVE_UPGRADE_1 = "I needed that upgrade. Thank you."; DAINA_ARMORGIVE_UPGRADE_2 = "Wonderful!"; DAINA_ARMORGIVE_UPGRADE_3 = "Awesome."; +DAINA_ARMORGIVE_WORNOVER_1 = "Thanks, this will go great with the rest of my outfit."; +DAINA_ARMORGIVE_WORNOVER_2 = "Ooh, another one!"; +DAINA_ARMORGIVE_WORNOVER_3 = "This gives me more chances for flair! Thanks."; + DAINA_ARMORGIVE_BETTERDURABILITY_1 = "Thanks, this will last much longer than my old armor."; DAINA_ARMORGIVE_BETTERDURABILITY_2 = "Much better!"; DAINA_ARMORGIVE_BETTERDURABILITY_3 = "This gives me a much better sense of security. Thanks."; diff --git a/LANGUAGE.Freylis b/LANGUAGE.Freylis index f005a70..436fb28 100644 --- a/LANGUAGE.Freylis +++ b/LANGUAGE.Freylis @@ -148,6 +148,10 @@ FREYLIS_ARMORGIVE_UPGRADE_1 = "Thank you, this one is much better."; FREYLIS_ARMORGIVE_UPGRADE_2 = "I really appreciate your concern for my well-being."; FREYLIS_ARMORGIVE_UPGRADE_3 = "Great stuff. Thanks."; +FREYLIS_ARMORGIVE_WORNOVER_1 = "I hope it comes with more pocket space."; +FREYLIS_ARMORGIVE_WORNOVER_2 = "I'm just glad it fits."; +FREYLIS_ARMORGIVE_WORNOVER_3 = "Another one couldn't hurt."; + FREYLIS_ARMORGIVE_BETTERDURABILITY_1 = "Better than the old one, that's for sure."; FREYLIS_ARMORGIVE_BETTERDURABILITY_2 = "Has fewer dents than the old one."; FREYLIS_ARMORGIVE_BETTERDURABILITY_3 = "Definitely sturdier than what I have."; diff --git a/LANGUAGE.Nari b/LANGUAGE.Nari index a71af63..2620d4a 100644 --- a/LANGUAGE.Nari +++ b/LANGUAGE.Nari @@ -142,6 +142,10 @@ NARI_ARMORGIVE_UPGRADE_1 = "I needed that upgrade. Thank you."; NARI_ARMORGIVE_UPGRADE_2 = "Wonderful!"; NARI_ARMORGIVE_UPGRADE_3 = "Awesome."; +NARI_ARMORGIVE_WORNOVER_1 = "Thanks, this will go great with the rest of my outfit."; +NARI_ARMORGIVE_WORNOVER_2 = "Ooh, another one!"; +NARI_ARMORGIVE_WORNOVER_3 = "This gives me more chances for flair! Thanks."; + NARI_ARMORGIVE_BETTERDURABILITY_1 = "Thanks, this will last much longer than my old armor."; NARI_ARMORGIVE_BETTERDURABILITY_2 = "Much better!"; NARI_ARMORGIVE_BETTERDURABILITY_3 = "This gives me a much better sense of security. Thanks."; diff --git a/LANGUAGE.RecruitedMarine b/LANGUAGE.RecruitedMarine index 8202c50..0541464 100644 --- a/LANGUAGE.RecruitedMarine +++ b/LANGUAGE.RecruitedMarine @@ -138,6 +138,10 @@ MARINE_ARMORGIVE_UPGRADE_1 = "Thank you, commander."; MARINE_ARMORGIVE_UPGRADE_2 = "Awesome."; MARINE_ARMORGIVE_UPGRADE_3 = "Good stuff, commander."; +MARINE_ARMORGIVE_WORNOVER_1 = "I'll put it to good use, commander."; +MARINE_ARMORGIVE_WORNOVER_2 = "Does it come with more pockets, commander?"; +MARINE_ARMORGIVE_WORNOVER_3 = "Another layer of protection."; + MARINE_ARMORGIVE_BETTERDURABILITY_1 = "Much obliged, commander."; MARINE_ARMORGIVE_BETTERDURABILITY_2 = "Bless you, commander."; MARINE_ARMORGIVE_BETTERDURABILITY_3 = "This will last much longer."; diff --git a/LANGUAGE.Sewie b/LANGUAGE.Sewie index 56f6a6a..9648b0c 100644 --- a/LANGUAGE.Sewie +++ b/LANGUAGE.Sewie @@ -146,6 +146,10 @@ SEWIE_ARMORGIVE_UPGRADE_1 = "I needed that upgrade. Thank you."; SEWIE_ARMORGIVE_UPGRADE_2 = "Wonderful!"; SEWIE_ARMORGIVE_UPGRADE_3 = "Awesome."; +SEWIE_ARMORGIVE_WORNOVER_1 = "Thanks, this will go great with the rest of my outfit."; +SEWIE_ARMORGIVE_WORNOVER_2 = "Ooh, another one!"; +SEWIE_ARMORGIVE_WORNOVER_3 = "This gives me more chances for flair! Thanks."; + SEWIE_ARMORGIVE_BETTERDURABILITY_1 = "Thanks, this will last much longer than my old armor."; SEWIE_ARMORGIVE_BETTERDURABILITY_2 = "Much better!"; SEWIE_ARMORGIVE_BETTERDURABILITY_3 = "This gives me a much better sense of security. Thanks."; diff --git a/zscript/CommandMenu.zsc b/zscript/CommandMenu.zsc index 6ec1051..10abad2 100644 --- a/zscript/CommandMenu.zsc +++ b/zscript/CommandMenu.zsc @@ -915,27 +915,45 @@ extend class FollowerHandler sb.Fill(col, pos.x, pos.y, size.x, size.y, flags); sb.Fill(Color(DarkAlpha, 0, 0, 0), pos.x + 1, pos.y + 1, size.x - 2, size.y - 2, flags); - if (wearable is 'HDWeapon') + if (wearable is 'HDStorageItem') { + if (!(Level.time % TICRATE)) HDCore.log("Follower."..getClassName(), LOGGING_DEBUG, "Drawing HDStorageItem Wearable: '"..wearable.getClassName().."'..."); + sb.DrawImage(""..wearable.Icon, pos + size / 2, flags | sb.DI_ITEM_CENTER, box: size - (3, 3)); - sb.DrawString(fnt, sb.FormatNumber(HDWeapon(wearable).GetSBarNum(), 1, 4), pos + size - (4, 10), flags | sb.DI_TEXT_ALIGN_RIGHT, sb.SavedColour); + sb.DrawString(fnt, "\c[Olive]"..sb.FormatNumber(HDStorageItem(wearable).GetSBarNum(), 1, 4), pos + size - (4, 10), flags | sb.DI_TEXT_ALIGN_RIGHT); } + else if (wearable is 'HDWeapon') + { + if (!(Level.time % TICRATE)) HDCore.log("Follower."..getClassName(), LOGGING_DEBUG, "Drawing HDWeapon Wearable: '"..wearable.getClassName().."'..."); - if (wearable is 'HDArmourWorn') + sb.DrawImage(""..wearable.Icon, pos + size / 2, flags | sb.DI_ITEM_CENTER, box: size - (3, 3)); + sb.DrawString(fnt, sb.FormatNumber(HDWeapon(wearable).GetSBarNum(), 1, 4), pos + size - (4, 10), flags | sb.DI_TEXT_ALIGN_RIGHT, sb.SavedColour); + } + else if (wearable is 'HDArmourWorn') { - if (wearable.bDROPTRANSLATION) { + if (!(Level.time % TICRATE)) HDCore.log("Follower."..getClassName(), LOGGING_DEBUG, "Drawing HDArmourWorn Wearable: '"..wearable.getClassName().."'..."); + + if (HDArmourWorn(wearable).bDROPTRANSLATION) { sb.DrawImage(""..HDArmourWorn(wearable).armoursprite, pos + size / 2, flags | sb.DI_ITEM_CENTER | sb.DI_TRANSLATABLE, box: size - (3, 3), translation: wearable.owner.translation); } else { sb.DrawImage(""..HDArmourWorn(wearable).armoursprite, pos + size / 2, flags | sb.DI_ITEM_CENTER, box: size - (3, 3)); } sb.DrawString(fnt, "\c[White]"..HDArmourWorn(wearable).durability, pos + size - (4, 10), flags | sb.DI_TEXT_ALIGN_RIGHT); } - - if (wearable is 'HDMagicShield') + else if (wearable is 'HDMagicShield') { + if (!(Level.time % TICRATE)) HDCore.log("Follower."..getClassName(), LOGGING_DEBUG, "Drawing HDMagicShield Wearable: '"..wearable.getClassName().."'..."); + sb.DrawImage(""..wearable.Icon, pos + size / 2, flags | sb.DI_ITEM_CENTER, box: size - (3, 3)); sb.DrawString(fnt, "\c[Cyan]"..HDMagicShield(wearable).amount, pos + size - (4, 10), flags | sb.DI_TEXT_ALIGN_RIGHT); } + else + { + if (!(Level.time % TICRATE)) HDCore.log("Follower."..getClassName(), LOGGING_WARN, "Attempting to Draw Unknown Wearable: '"..wearable.getClassName().."'..."); + + sb.DrawImage(""..wearable.Icon, pos + size / 2, flags | sb.DI_ITEM_CENTER, box: size - (3, 3)); + sb.DrawString(fnt, "\c[DarkGray]"..wearable.amount, pos + size - (4, 10), flags | sb.DI_TEXT_ALIGN_RIGHT); + } } private ui void DrawRectangle(HDStatusBar sb, vector2 pos, vector2 size, Color col, HUDFont fnt, string command, int flags, int textCol = Font.CR_WHITE) diff --git a/zscript/Wearables.zsc b/zscript/Wearables.zsc index 1fa4dde..5ff85a6 100644 --- a/zscript/Wearables.zsc +++ b/zscript/Wearables.zsc @@ -23,8 +23,12 @@ extend class HDFollower private void LookForWearable() { + HDCore.log("Follower."..getClassName(), LOGGING_DEBUG, "Looking For Wearable..."); + if (!WearableInfo.GetWorldItem() || !WearableInfo.GetWornItem()) { + HDCore.log("Follower."..getClassName(), LOGGING_WARN, "Invalid WorldItem or WornItem."); + return; } @@ -40,27 +44,30 @@ extend class HDFollower let foundWearable = Inventory(tracer); let wornWearable = FindInventory(WearableInfo.WornItemCls, true); - let hw = HDWeapon(foundWearable); - let hp = HDPickup(foundWearable); - if (foundWearable) { + HDCore.log("Follower."..getClassName(), LOGGING_DEBUG, "Found Wearable: '"..foundWearable.getClassName().."'"); + + let ha = HDArmour(foundWearable); + let hm = HDMagAmmo(foundWearable); + + int haWornLayer; + + if (ha) { + let haWornCls = (Class)(ha.wornName()); + let haWornDefs = GetDefaultByType(haWornCls); + haWornLayer = HDArmourWorn(haWornDefs).wornlayer; + } + if (!wornWearable) { PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_NONE"), WearableInfo.SoundPrefix.."/None", PMType_General); } - else if (wornWearable is 'HDArmourWorn') + else if (HDCore.isChildClass(wornWearable.getClass(), 'HDMagicShield')) { - let foundArmor = HDArmour(foundWearable); - let wornArmor = HDArmourWorn(wornWearable); - - // [Ace] 1030 because mega armor's durability is calculated as 1000 + actual durability. Only while on the ground, though. - // [UZ] 2026-04-21: Armour Refactor removed "mega" property, now split into individual actors - // TODO: Generify Armor Preference/Priority - // For Now, simply replace via scaled durability - if (((1.0 * wornArmor.Durability) / wornArmor.default.Durability) < ((1.0 * foundArmor.Mags[foundArmor.Mags.Size() - 1]) / foundArmor.maxperunit)) + if (wornWearable.amount < hm.mags[hm.mags.size() - 1]) { - PrintMessage(GetResponse("ARMORGIVE_BETTERDURABILITY"), "ArmorGive/BetterDurability", PMType_General); + PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_BETTERDURABILITY"), WearableInfo.SoundPrefix.."/BetterDurability", PMType_General); } else { @@ -70,6 +77,52 @@ extend class HDFollower return; } } + else if (HDCore.isChildClass(wornWearable.getClass(), 'HDArmourWorn')) + { + HDCore.log("Follower."..getClassName(), LOGGING_DEBUG, "Wearing Armor: '"..wornWearable.getClassName().."'"); + + // Loop through all possible instances of HDArmourWorn, + // in case the order in which they're in the Follower's inventory + // causes HHelmet to be selected first before any others, + // invalidating the checks below + HDArmourWorn wearableSameWornLayer; + HDArmourWorn wearableHighestLayer; + for (let i = inv; i; i = i.inv) + { + if (HDCore.isChildClass(i.getClass(), 'HDArmourWorn')) + { + let w = HDArmourWorn(i); + + if (w.wornLayer > 0) + { + if (w.wornLayer == haWornLayer) wearableSameWornLayer = w; + + if (!wearableHighestLayer || w.wornLayer > wearableHighestLayer.wornLayer) wearableHighestLayer = w; + } + } + + if (wearableSameWornLayer && wearableHighestLayer) break; + } + + if (wearableSameWornLayer) HDCore.log("Follower."..getClassName(), LOGGING_DEBUG, "Wearing '"..wearableSameWornLayer.getClassName().."' on same layer ("..wearableSameWornLayer.wornlayer..")..."); + if (wearableHighestLayer) HDCore.log("Follower."..getClassName(), LOGGING_DEBUG, "Wearing '"..wearableHighestLayer.getClassName().."' at outermost layer ("..wearableHighestLayer.wornlayer..")..."); + + if (wearableHighestLayer && haWornLayer > wearableHighestLayer.wornLayer) + { + PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_WORNOVER"), WearableInfo.SoundPrefix.."/WornOver", PMType_General); + } + else if (wearableSameWornLayer && ((1.0 * wearableSameWornLayer.Durability) / wearableSameWornLayer.default.Durability) < ((1.0 * ha.Mags[ha.Mags.Size() - 1]) / ha.maxperunit)) + { + PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_BETTERDURABILITY"), WearableInfo.SoundPrefix.."/BetterDurability", PMType_General); + } + else if (ha.wornLayer > 0) + { + PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_NOTHANKS"), WearableInfo.SoundPrefix.."/NoThanks", PMType_Important); + Status = FStatus_None; + tracer = null; + return; + } + } else { PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_NOTHANKS"), WearableInfo.SoundPrefix.."/NoThanks", PMType_Important); @@ -78,16 +131,72 @@ extend class HDFollower return; } - if ((hp && hp.wornlayer > 0) || (hw && hw.wornlayer > 0)) while (!HDPlayerPawn.CheckStrip(self, foundWearable)); + let hw = HDWeapon(foundWearable); + let hp = HDPickup(foundWearable); + + if ((ha && haWornLayer > 0) || (hp && hp.wornlayer > 0) || (hw && hw.wornlayer > 0)) { + let tempWornLayer = ha ? ha.wornLayer : 1; + + if (ha && haWornLayer != tempWornLayer) ha.wornLayer = haWornLayer; + + while (!HDPlayerPawn.CheckStrip(self, foundWearable)) HDCore.log("Follower."..getClassName(), LOGGING_DEBUG, "Stripping Wearable."); + + if (ha) ha.wornLayer = tempWornLayer; + } if (hw) { + HDCore.log("Follower."..getClassName(), LOGGING_DEBUG, "Giving HDWeapon Wearable."); hw.ActualPickup(self); } + else if (hm) + { + if (HDCore.isChildClass(hm.getClass(), 'ShieldCore')) + { + HDCore.log("Follower."..getClassName(), LOGGING_DEBUG, "Giving ShieldCore."); + + HDF.Give(self, "HDMagicShield"); + let sss = HDPickup(FindInventory("HDMagicShield")); + if (sss) + { + sss.amount = 1; + sss.maxamount = hm.mags[hm.mags.size() - 1]; + sss.bulk = hm.magBulk; + sss.mass = sss.maxAmount - 1; + sss.bQUICKTORETALIATE = false; + + if (sss.amount > 0) HDMagicShield.FlashSparks(self); + } + + hm.destroy(); + } + else if (HDCore.isChildClass(hm.getClass(), 'HDArmour')) + { + HDCore.log("Follower."..getClassName(), LOGGING_DEBUG, "Giving HDArmour Wearable."); + + let wornName = HDArmour(hm).wornName(); + HDF.Give(self, wornName); + let worn = HDArmourWorn(FindInventory(wornName)); + worn.Durability = hm.mags[hm.mags.size() - 1]; + + hm.destroy(); + } + else + { + HDCore.log("Follower."..getClassName(), LOGGING_DEBUG, "Giving HDMagAmmo Wearable."); + + let cls = hm.getClass(); + hm.ActualPickup(self); + if (!UseInventory(FindInventory(cls))) HDCore.log("Follower."..getClassName(), LOGGING_WARN, "Failed to wear '"..(hm ? hm.getClassName() : cls.getClassName()).."'."); + } + } else if (hp) { + HDCore.log("Follower."..getClassName(), LOGGING_DEBUG, "Giving HDPickup Wearble."); + + let cls = hm.getClass(); hp.ActualPickup(self); - UseInventory(FindInventory(foundWearable.GetClass())); + if (!UseInventory(FindInventory(cls))) HDCore.log("Follower."..getClassName(), LOGGING_WARN, "Failed to wear '"..(hm ? hm.getClassName() : cls.getClassName()).."'."); } Status = FStatus_None; } @@ -96,116 +205,27 @@ extend class HDFollower void TryUseWearable() { + HDCore.log("Follower."..getClassName(), LOGGING_DEBUG, "Trying to use Wearable..."); - if (!WearableInfo.GetWorldItem() || !WearableInfo.GetWornItem()) return; + if (!WearableInfo.GetWorldItem() || !WearableInfo.GetWornItem()) + { + HDCore.log("Follower."..getClassName(), LOGGING_WARN, "Invalid WorldItem or WornItem."); + return; + } switch (Status) { case FStatus_InactiveOrDead: - // no-op + HDCore.log("Follower."..getClassName(), LOGGING_DEBUG, "Can't use, am ded."); break; case FStatus_WaitingForWearable: - - CheckProximity('HDArmour', radius + 24, 1, CPXF_ANCESTOR|CPXF_CLOSEST|CPXF_SETTRACER); - let FoundArmor = HDArmour(tracer); - let WornArmor = HDArmourWorn(FindInventory('HDArmourWorn', true)); - - CheckProximity('HDStorageItem', radius + 24, 1, CPXF_ANCESTOR|CPXF_CLOSEST|CPXF_SETTRACER); - let FoundBackpack = HDStorageItem(tracer); - let WornBackpack = HDStorageItem(FindInventory('HDStorageItem', true)); - - CheckProximity('ShieldCore', radius + 24, 1, CPXF_CLOSEST|CPXF_SETTRACER); - let FoundShield = ShieldCore(tracer); - let WornShield = HDMagicShield(FindInventory('HDMagicShield')); - - if (FoundArmor) - { - if (WornArmor) - { - // [Ace] 1030 because mega armor's durability is calculated as 1000 + actual durability. Only while on the ground, though. - // [UZ] 2026-04-21: Armour Refactor removed "mega" property, now split into individual actors - // TODO: Generify Armor Preference/Priority - // For Now, simply replace via scaled durability - if (((1.0 * WornArmor.Durability) / WornArmor.default.Durability) < ((1.0 * FoundArmor.Mags[FoundArmor.Mags.Size() - 1]) / FoundArmor.maxperunit)) - { - PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_BETTERDURABILITY"), WearableInfo.SoundPrefix.."/BetterDurability", PMType_General); - - A_DropInventory(WornArmor.GetClass()); - } - else - { - PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_NOTHANKS"), WearableInfo.SoundPrefix.."/NoThanks", PMType_Important); - Status = FStatus_None; - tracer = null; - return; - } - } - else - { - PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_NONE"), WearableInfo.SoundPrefix.."/None", PMType_General); - } - - - FoundArmor.ActualPickup(self); - if (FoundArmor.bDROPTRANSLATION) FoundArmor.translation = translation; - UseInventory(FoundArmor); - Status = FStatus_None; - return; - } - else if (FoundBackpack) - { - if (WornBackpack) - { - PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_CANCEL"), WearableInfo.SoundPrefix.."/Cancel", PMType_Important); - Status = FStatus_None; - tracer = null; - return; - } - else - { - PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_NONE"), WearableInfo.SoundPrefix.."/None", PMType_General); - } - - FoundBackpack.ActualPickup(self); - Status = FStatus_None; - return; - } - else if (FoundShield) - { - if (WornShield) - { - if (FoundShield.mags[FoundShield.mags.size() - 1] > WornShield.amount) - { - PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_BETTERDURABILITY"), WearableInfo.SoundPrefix.."/BetterDurability", PMType_General); - - A_DropInventory(WornShield.GetClass()); - } - else - { - PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_NOTHANKS"), WearableInfo.SoundPrefix.."/NoThanks", PMType_Important); - Status = FStatus_None; - tracer = null; - return; - } - } - else - { - PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_NONE"), WearableInfo.SoundPrefix.."/None", PMType_General); - } + HDCore.log("Follower."..getClassName(), LOGGING_DEBUG, "Cancelling waiting for Wearable..."); - FoundShield.ActualPickup(self); - UseInventory(FoundShield); - Status = FStatus_None; - return; - } - else - { - PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_CANCEL"), WearableInfo.SoundPrefix.."/Cancel", PMType_Important); - } - - tracer = null; + PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_CANCEL"), WearableInfo.SoundPrefix.."/Cancel", PMType_Important); Status = FStatus_None; break; default: + HDCore.log("Follower."..getClassName(), LOGGING_DEBUG, "Going to wait for Wearable..."); + Status = FStatus_WaitingForWearable; PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_INIT"), WearableInfo.SoundPrefix.."/Init", PMType_Important); break; @@ -222,7 +242,7 @@ extend class HDFollower let wornItem = FindInventory(WearableInfo.WornItemCls, true); if (wornItem) { - A_DropInventory(wornItem.GetClass()); + A_DropInventory(wornItem.getClass()); PrintMessage(GetResponse(WearableInfo.LanguagePrefix.."_STRIP"), WearableInfo.SoundPrefix.."/Strip", PMType_General); Status = FStatus_None; }