diff --git a/src/d_dehacked.cpp b/src/d_dehacked.cpp index abe25a753..f0054b290 100644 --- a/src/d_dehacked.cpp +++ b/src/d_dehacked.cpp @@ -1948,19 +1948,18 @@ static int PatchMisc (int dummy) // Update default item properties by patching the affected items // Note: This won't have any effect on DECORATE derivates of these items! - ABasicArmorPickup *armor; - armor = static_cast (GetDefaultByName ("GreenArmor")); + auto armor = GetDefaultByName ("GreenArmor"); if (armor!=NULL) { - armor->SaveAmount = 100 * deh.GreenAC; - armor->SavePercent = deh.GreenAC == 1 ? 0.33335 : 0.5; + armor->IntVar(NAME_SaveAmount) = 100 * deh.GreenAC; + armor->FloatVar(NAME_SavePercent) = deh.GreenAC == 1 ? 0.33335 : 0.5; } - armor = static_cast (GetDefaultByName ("BlueArmor")); + armor = GetDefaultByName ("BlueArmor"); if (armor!=NULL) { - armor->SaveAmount = 100 * deh.BlueAC; - armor->SavePercent = deh.BlueAC == 1 ? 0.33335 : 0.5; + armor->IntVar(NAME_SaveAmount) = 100 * deh.BlueAC; + armor->FloatVar(NAME_SavePercent) = deh.BlueAC == 1 ? 0.33335 : 0.5; } auto barmor = GetDefaultByName ("ArmorBonus"); diff --git a/src/g_inventory/a_armor.cpp b/src/g_inventory/a_armor.cpp index 5f4b0dbbe..816cf1620 100644 --- a/src/g_inventory/a_armor.cpp +++ b/src/g_inventory/a_armor.cpp @@ -47,7 +47,6 @@ IMPLEMENT_CLASS(AArmor, false, false) IMPLEMENT_CLASS(ABasicArmor, false, false) -IMPLEMENT_CLASS(ABasicArmorPickup, false, false) IMPLEMENT_CLASS(AHexenArmor, false, false) //=========================================================================== @@ -85,231 +84,6 @@ void ABasicArmor::Serialize(FSerializer &arc) ("actualsaveamount", ActualSaveAmount, def->ActualSaveAmount); } -//=========================================================================== -// -// ABasicArmor :: Tick -// -// If BasicArmor is given to the player by means other than a -// BasicArmorPickup, then it may not have an icon set. Fix that here. -// -//=========================================================================== - -void ABasicArmor::Tick () -{ - Super::Tick (); - AbsorbCount = 0; - if (!Icon.isValid()) - { - FString icon = gameinfo.ArmorIcon1; - - if (SavePercent >= gameinfo.Armor2Percent && gameinfo.ArmorIcon2.Len() != 0) - icon = gameinfo.ArmorIcon2; - - if (icon[0] != 0) - Icon = TexMan.CheckForTexture (icon, FTexture::TEX_Any); - } -} - -//=========================================================================== -// -// ABasicArmor :: CreateCopy -// -//=========================================================================== - -AInventory *ABasicArmor::CreateCopy (AActor *other) -{ - // BasicArmor that is in use is stored in the inventory as BasicArmor. - // BasicArmor that is in reserve is not. - ABasicArmor *copy = Spawn (); - copy->SavePercent = SavePercent != 0 ? SavePercent : 0.33335; // slightly more than 1/3 to avoid roundoff errors. - copy->Amount = Amount; - copy->MaxAmount = MaxAmount; - copy->Icon = Icon; - copy->BonusCount = BonusCount; - copy->ArmorType = ArmorType; - copy->ActualSaveAmount = ActualSaveAmount; - GoAwayAndDie (); - return copy; -} - -//=========================================================================== -// -// ABasicArmor :: AbsorbDamage -// -//=========================================================================== - -void ABasicArmor::AbsorbDamage (int damage, FName damageType, int &newdamage) -{ - int saved; - - if (!DamageTypeDefinition::IgnoreArmor(damageType)) - { - int full = MAX(0, MaxFullAbsorb - AbsorbCount); - if (damage < full) - { - saved = damage; - } - else - { - saved = full + int((damage - full) * SavePercent); - if (MaxAbsorb > 0 && saved + AbsorbCount > MaxAbsorb) - { - saved = MAX(0, MaxAbsorb - AbsorbCount); - } - } - - if (Amount < saved) - { - saved = Amount; - } - newdamage -= saved; - Amount -= saved; - AbsorbCount += saved; - if (Amount == 0) - { - // The armor has become useless - SavePercent = 0; - ArmorType = NAME_None; // Not NAME_BasicArmor. - // Now see if the player has some more armor in their inventory - // and use it if so. As in Strife, the best armor is used up first. - ABasicArmorPickup *best = NULL; - AInventory *probe = Owner->Inventory; - while (probe != NULL) - { - if (probe->IsKindOf (PClass::FindActor(NAME_BasicArmorPickup))) - { - ABasicArmorPickup *inInv = static_cast(probe); - if (best == NULL || best->SavePercent < inInv->SavePercent) - { - best = inInv; - } - } - probe = probe->Inventory; - } - if (best != NULL) - { - Owner->UseInventory (best); - } - } - damage = newdamage; - } - - // Once the armor has absorbed its part of the damage, then apply its damage factor, if any, to the player - if ((damage > 0) && (ArmorType != NAME_None)) // BasicArmor is not going to have any damage factor, so skip it. - { - // This code is taken and adapted from APowerProtection::ModifyDamage(). - // The differences include not using a default value, and of course the way - // the damage factor info is obtained. - - // ApplyDamageFactors(ArmorType, damageType, damage, damage); - DmgFactors *df = PClass::FindActor(ArmorType)->DamageFactors; - if (df != NULL) - { - damage = newdamage = df->Apply(damageType, damage); - } - } -} - -//=========================================================================== -// -// -// BasicArmorPickup -// -// -//=========================================================================== - - -DEFINE_FIELD(ABasicArmorPickup, SavePercent) -DEFINE_FIELD(ABasicArmorPickup, MaxAbsorb) -DEFINE_FIELD(ABasicArmorPickup, MaxFullAbsorb) -DEFINE_FIELD(ABasicArmorPickup, SaveAmount) - -//=========================================================================== -// -// ABasicArmorPickup :: Serialize -// -//=========================================================================== - -void ABasicArmorPickup::Serialize(FSerializer &arc) -{ - Super::Serialize (arc); - - auto def = (ABasicArmorPickup *)GetDefault(); - arc("savepercent", SavePercent, def->SavePercent) - ("saveamount", SaveAmount, def->SaveAmount) - ("maxabsorb", MaxAbsorb, def->MaxAbsorb) - ("maxfullabsorb", MaxFullAbsorb, def->MaxFullAbsorb); -} - -//=========================================================================== -// -// ABasicArmorPickup :: CreateCopy -// -//=========================================================================== - -AInventory *ABasicArmorPickup::CreateCopy (AActor *other) -{ - ABasicArmorPickup *copy = static_cast (Super::CreateCopy (other)); - - if (!(ItemFlags & IF_IGNORESKILL)) - { - SaveAmount = int(SaveAmount * G_SkillProperty(SKILLP_ArmorFactor)); - } - - copy->SavePercent = SavePercent; - copy->SaveAmount = SaveAmount; - copy->MaxAbsorb = MaxAbsorb; - copy->MaxFullAbsorb = MaxFullAbsorb; - - return copy; -} - -//=========================================================================== -// -// ABasicArmorPickup :: Use -// -// Either gives you new armor or replaces the armor you already have (if -// the SaveAmount is greater than the amount of armor you own). When the -// item is auto-activated, it will only be activated if its max amount is 0 -// or if you have no armor active already. -// -//=========================================================================== - -bool ABasicArmorPickup::Use (bool pickup) -{ - ABasicArmor *armor = Owner->FindInventory (); - - if (armor == NULL) - { - armor = Spawn (); - armor->BecomeItem (); - Owner->AddInventory (armor); - } - else - { - // If you already have more armor than this item gives you, you can't - // use it. - if (armor->Amount >= SaveAmount + armor->BonusCount) - { - return false; - } - // Don't use it if you're picking it up and already have some. - if (pickup && armor->Amount > 0 && MaxAmount > 0) - { - return false; - } - } - armor->SavePercent = SavePercent; - armor->Amount = SaveAmount + armor->BonusCount; - armor->MaxAmount = SaveAmount; - armor->Icon = Icon; - armor->MaxAbsorb = MaxAbsorb; - armor->MaxFullAbsorb = MaxFullAbsorb; - armor->ArmorType = this->GetClass()->TypeName; - armor->ActualSaveAmount = SaveAmount; - return true; -} - //=========================================================================== // // diff --git a/src/g_inventory/a_armor.h b/src/g_inventory/a_armor.h index 7bfb40a0a..c09162f72 100644 --- a/src/g_inventory/a_armor.h +++ b/src/g_inventory/a_armor.h @@ -17,9 +17,6 @@ class ABasicArmor : public AArmor public: virtual void Serialize(FSerializer &arc) override; - virtual void Tick () override; - virtual AInventory *CreateCopy (AActor *other) override; - virtual void AbsorbDamage (int damage, FName damageType, int &newdamage) override; int AbsorbCount; double SavePercent; @@ -30,22 +27,6 @@ public: int ActualSaveAmount; }; -// BasicArmorPickup replaces the armor you have. -class ABasicArmorPickup : public AArmor -{ - DECLARE_CLASS (ABasicArmorPickup, AArmor) -public: - - virtual void Serialize(FSerializer &arc) override; - virtual AInventory *CreateCopy (AActor *other) override; - virtual bool Use (bool pickup) override; - - double SavePercent; - int MaxAbsorb; - int MaxFullAbsorb; - int SaveAmount; -}; - // Hexen armor consists of four separate armor types plus a conceptual armor // type (the player himself) that work together as a single armor. class AHexenArmor : public AArmor diff --git a/src/gi.cpp b/src/gi.cpp index a13804aad..6c7349592 100644 --- a/src/gi.cpp +++ b/src/gi.cpp @@ -47,6 +47,8 @@ gameinfo_t gameinfo; DEFINE_FIELD_X(GameInfoStruct, gameinfo_t, backpacktype) DEFINE_FIELD_X(GameInfoStruct, gameinfo_t, Armor2Percent) +DEFINE_FIELD_X(GameInfoStruct, gameinfo_t, ArmorIcon1) +DEFINE_FIELD_X(GameInfoStruct, gameinfo_t, ArmorIcon2) const char *GameNames[17] = diff --git a/src/info.cpp b/src/info.cpp index bdfcb4057..a424c890f 100644 --- a/src/info.cpp +++ b/src/info.cpp @@ -758,6 +758,14 @@ bool DamageTypeDefinition::IgnoreArmor(FName type) return false; } +DEFINE_ACTION_FUNCTION(_DamageTypeDefinition, IgnoreArmor) +{ + PARAM_PROLOGUE; + PARAM_NAME(type); + ACTION_RETURN_BOOL(DamageTypeDefinition::IgnoreArmor(type)); +} + + //========================================================================== // // DamageTypeDefinition :: ApplyMobjDamageFactor diff --git a/src/info.h b/src/info.h index 54aac3e3c..4a6baa671 100644 --- a/src/info.h +++ b/src/info.h @@ -226,10 +226,12 @@ public: NoArmor = false; } - static DamageTypeDefinition *Get(FName type); static bool IgnoreArmor(FName type); - static double GetMobjDamageFactor(FName type, DmgFactors const * const factors); static int ApplyMobjDamageFactor(int damage, FName type, DmgFactors const * const factors); + +private: + static double GetMobjDamageFactor(FName type, DmgFactors const * const factors); + static DamageTypeDefinition *Get(FName type); }; class DDropItem; diff --git a/src/scripting/thingdef_properties.cpp b/src/scripting/thingdef_properties.cpp index 08a394ffe..9d2423154 100644 --- a/src/scripting/thingdef_properties.cpp +++ b/src/scripting/thingdef_properties.cpp @@ -1723,79 +1723,6 @@ DEFINE_CLASS_PROPERTY(forbiddento, Ssssssssssssssssssss, Inventory) } } -//========================================================================== -// -//========================================================================== -DEFINE_CLASS_PROPERTY(saveamount, I, Armor) -{ - PROP_INT_PARM(i, 0); - - // Special case here because this property has to work for 2 unrelated classes - if (info->IsDescendantOf(PClass::FindActor(NAME_BasicArmorPickup)) || info->IsDescendantOf(PClass::FindActor(NAME_BasicArmorBonus))) - { - defaults->IntVar(NAME_SaveAmount)=i; - } - else - { - I_Error("\"Armor.SaveAmount\" requires an actor of type \"Armor\""); - } -} - -//========================================================================== -// -//========================================================================== -DEFINE_CLASS_PROPERTY(savepercent, F, Armor) -{ - PROP_DOUBLE_PARM(i, 0); - - i = clamp(i, 0., 100.)/100.; - // Special case here because this property has to work for 2 unrelated classes - if (info->IsDescendantOf(PClass::FindActor(NAME_BasicArmorPickup)) || info->IsDescendantOf(PClass::FindActor(NAME_BasicArmorBonus))) - { - defaults->FloatVar(NAME_SavePercent) = i; - } - else - { - I_Error("\"Armor.SavePercent\" requires an actor of type \"Armor\"\n"); - } -} - -//========================================================================== -// -//========================================================================== -DEFINE_CLASS_PROPERTY(maxabsorb, I, Armor) -{ - PROP_INT_PARM(i, 0); - - // Special case here because this property has to work for 2 unrelated classes - if (info->IsDescendantOf(PClass::FindActor(NAME_BasicArmorPickup)) || info->IsDescendantOf(PClass::FindActor(NAME_BasicArmorBonus))) - { - defaults->IntVar(NAME_MaxAbsorb) = i; - } - else - { - I_Error("\"Armor.MaxAbsorb\" requires an actor of type \"Armor\"\n"); - } -} - -//========================================================================== -// -//========================================================================== -DEFINE_CLASS_PROPERTY(maxfullabsorb, I, Armor) -{ - PROP_INT_PARM(i, 0); - - // Special case here because this property has to work for 2 unrelated classes - if (info->IsDescendantOf(PClass::FindActor(NAME_BasicArmorPickup)) || info->IsDescendantOf(PClass::FindActor(NAME_BasicArmorBonus))) - { - defaults->IntVar(NAME_MaxFullAbsorb) = i; - } - else - { - I_Error("\"Armor.MaxFullAbsorb\" requires an actor of type \"Armor\"\n"); - } -} - //========================================================================== // //========================================================================== diff --git a/wadsrc/static/zscript/base.txt b/wadsrc/static/zscript/base.txt index 9cad3224e..2cf48363b 100644 --- a/wadsrc/static/zscript/base.txt +++ b/wadsrc/static/zscript/base.txt @@ -42,11 +42,18 @@ struct Console native native static void HideConsole(); } +struct DamageTypeDefinition native +{ + native static bool IgnoreArmor(Name type); +} + struct GameInfoStruct native { // will be extended as needed. native Name backpacktype; native double Armor2Percent; + native String ArmorIcon1; + native String ArmorIcon2; } class Object native diff --git a/wadsrc/static/zscript/inventory/armor.txt b/wadsrc/static/zscript/inventory/armor.txt index 16bffa78e..1d0f5fd9d 100644 --- a/wadsrc/static/zscript/inventory/armor.txt +++ b/wadsrc/static/zscript/inventory/armor.txt @@ -1,3 +1,38 @@ +/* +** armor.txt +** Implements all variations of armor objects +** +**--------------------------------------------------------------------------- +** Copyright 2002-2016 Randy Heit +** Copyright 2006-2017 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + class Armor : Inventory native { Default @@ -6,6 +41,14 @@ class Armor : Inventory native } } +//=========================================================================== +// +// +// BasicArmor +// +// +//=========================================================================== + class BasicArmor : Armor native { @@ -21,7 +64,55 @@ class BasicArmor : Armor native { +Inventory.KEEPDEPLETED } - + + //=========================================================================== + // + // ABasicArmor :: Tick + // + // If BasicArmor is given to the player by means other than a + // BasicArmorPickup, then it may not have an icon set. Fix that here. + // + //=========================================================================== + + override void Tick () + { + Super.Tick (); + AbsorbCount = 0; + if (!Icon.isValid()) + { + String icontex = gameinfo.ArmorIcon1; + + if (SavePercent >= gameinfo.Armor2Percent && gameinfo.ArmorIcon2.Length() != 0) + icontex = gameinfo.ArmorIcon2; + + if (icontex.Length() != 0) + Icon = TexMan.CheckForTexture (icontex, TexMan.TYPE_Any); + } + } + + //=========================================================================== + // + // ABasicArmor :: CreateCopy + // + //=========================================================================== + + override Inventory CreateCopy (Actor other) + { + // BasicArmor that is in use is stored in the inventory as BasicArmor. + // BasicArmor that is in reserve is not. + let copy = BasicArmor(Spawn("BasicArmor")); + copy.SavePercent = SavePercent != 0 ? SavePercent : 0.33335; // slightly more than 1/3 to avoid roundoff errors. + copy.Amount = Amount; + copy.MaxAmount = MaxAmount; + copy.Icon = Icon; + copy.BonusCount = BonusCount; + copy.ArmorType = ArmorType; + copy.ActualSaveAmount = ActualSaveAmount; + GoAwayAndDie (); + return copy; + } + + //=========================================================================== // // ABasicArmor :: HandlePickup @@ -50,8 +141,86 @@ class BasicArmor : Armor native } return false; } + + //=========================================================================== + // + // ABasicArmor :: AbsorbDamage + // + //=========================================================================== + + override void AbsorbDamage (int damage, Name damageType, out int newdamage) + { + int saved; + + if (!DamageTypeDefinition.IgnoreArmor(damageType)) + { + int full = MAX(0, MaxFullAbsorb - AbsorbCount); + + if (damage < full) + { + saved = damage; + } + else + { + saved = full + int((damage - full) * SavePercent); + if (MaxAbsorb > 0 && saved + AbsorbCount > MaxAbsorb) + { + saved = MAX(0, MaxAbsorb - AbsorbCount); + } + } + + if (Amount < saved) + { + saved = Amount; + } + newdamage -= saved; + Amount -= saved; + AbsorbCount += saved; + if (Amount == 0) + { + // The armor has become useless + SavePercent = 0; + ArmorType = 'None'; // Not NAME_BasicArmor. + // Now see if the player has some more armor in their inventory + // and use it if so. As in Strife, the best armor is used up first. + BasicArmorPickup best = null; + Inventory probe = Owner.Inv; + while (probe != null) + { + let inInv = BasicArmorPickup(probe); + if (inInv != null) + { + if (best == null || best.SavePercent < inInv.SavePercent) + { + best = inInv; + } + } + probe = probe.Inv; + } + if (best != null) + { + Owner.UseInventory (best); + } + } + damage = newdamage; + } + + // Once the armor has absorbed its part of the damage, then apply its damage factor, if any, to the player + if ((damage > 0) && (ArmorType != 'None')) // BasicArmor is not going to have any damage factor, so skip it. + { + ApplyDamageFactors(ArmorType, damageType, damage, damage); + } + } } +//=========================================================================== +// +// +// BasicArmorBonus +// +// +//=========================================================================== + class BasicArmorBonus : Armor { double SavePercent; // The default, for when you don't already have armor @@ -119,7 +288,7 @@ class BasicArmorBonus : Armor bool result = false; // This should really never happen but let's be prepared for a broken inventory. - if (armor == NULL) + if (armor == null) { armor = BasicArmor(Spawn("BasicArmor")); armor.BecomeItem (); @@ -152,7 +321,7 @@ class BasicArmorBonus : Armor { // Should never be less than 0, but might as well check anyway armor.Amount = 0; armor.Icon = Icon; - armor.SavePercent = clamp(SavePercent, 0, 100); + armor.SavePercent = clamp(SavePercent, 0, 100) / 100; armor.MaxAbsorb = MaxAbsorb; armor.ArmorType = GetClassName(); armor.MaxFullAbsorb = MaxFullAbsorb; @@ -167,21 +336,114 @@ class BasicArmorBonus : Armor } -class BasicArmorPickup : Armor native +//=========================================================================== +// +// +// BasicArmorPickup +// +// +//=========================================================================== + +class BasicArmorPickup : Armor { - native double SavePercent; - native int MaxAbsorb; - native int MaxFullAbsorb; - native int SaveAmount; - + double SavePercent; + int MaxAbsorb; + int MaxFullAbsorb; + int SaveAmount; + + property prefix: Armor; + property SaveAmount : SaveAmount; + property SavePercent: SavePercent; + property MaxAbsorb: MaxAbsorb; + property MaxFullAbsorb: MaxFullAbsorb; + Default { +Inventory.AUTOACTIVATE; Inventory.MaxAmount 0; } + + //=========================================================================== + // + // ABasicArmorPickup :: CreateCopy + // + //=========================================================================== + + override Inventory CreateCopy (Actor other) + { + let copy = BasicArmorPickup(Super.CreateCopy (other)); + + if (!bIgnoreSkill) + { + SaveAmount = int(SaveAmount * G_SkillPropertyFloat(SKILLP_ArmorFactor)); + } + + copy.SavePercent = SavePercent; + copy.SaveAmount = SaveAmount; + copy.MaxAbsorb = MaxAbsorb; + copy.MaxFullAbsorb = MaxFullAbsorb; + + return copy; + } + + //=========================================================================== + // + // ABasicArmorPickup :: Use + // + // Either gives you new armor or replaces the armor you already have (if + // the SaveAmount is greater than the amount of armor you own). When the + // item is auto-activated, it will only be activated if its max amount is 0 + // or if you have no armor active already. + // + //=========================================================================== + + override bool Use (bool pickup) + { + let armor = BasicArmor(Owner.FindInventory("BasicArmor")); + + // This should really never happen but let's be prepared for a broken inventory. + if (armor == null) + { + armor = BasicArmor(Spawn("BasicArmor")); + armor.BecomeItem (); + Owner.AddInventory (armor); + } + else + { + // If you already have more armor than this item gives you, you can't + // use it. + if (armor.Amount >= SaveAmount + armor.BonusCount) + { + return false; + } + // Don't use it if you're picking it up and already have some. + if (pickup && armor.Amount > 0 && MaxAmount > 0) + { + return false; + } + } + + armor.SavePercent = clamp(SavePercent, 0, 100) / 100; + armor.Amount = SaveAmount + armor.BonusCount; + armor.MaxAmount = SaveAmount; + armor.Icon = Icon; + armor.MaxAbsorb = MaxAbsorb; + armor.MaxFullAbsorb = MaxFullAbsorb; + armor.ArmorType = GetClassName(); + armor.ActualSaveAmount = SaveAmount; + return true; + } } +//=========================================================================== +// +// +// HexenArmor +// +// +//=========================================================================== + class HexenArmor : Armor native {