mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-12-11 21:21:47 +00:00
12915b5f6e
- use an enum type for ItemFlags, just like it was done for actor flags. Since the flag word is almost full it may soon be necessary to add a second one and then this kind of security check may become necessary.
617 lines
16 KiB
Text
617 lines
16 KiB
Text
/*
|
|
** 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
|
|
{
|
|
Default
|
|
{
|
|
Inventory.PickupSound "misc/armor_pkup";
|
|
+INVENTORY.ISARMOR
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
//
|
|
// BasicArmor
|
|
//
|
|
// Basic armor absorbs a specific percent of the damage. You should
|
|
// never pickup a BasicArmor. Instead, you pickup a BasicArmorPickup
|
|
// or BasicArmorBonus and those gives you BasicArmor when it activates.
|
|
//
|
|
//
|
|
//===========================================================================
|
|
|
|
class BasicArmor : Armor
|
|
{
|
|
|
|
int AbsorbCount;
|
|
double SavePercent;
|
|
int MaxAbsorb;
|
|
int MaxFullAbsorb;
|
|
int BonusCount;
|
|
Name ArmorType;
|
|
int ActualSaveAmount;
|
|
|
|
Default
|
|
{
|
|
Inventory.Amount 0;
|
|
+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
|
|
//
|
|
//===========================================================================
|
|
|
|
override bool HandlePickup (Inventory item)
|
|
{
|
|
if (item.GetClass() == "BasicArmor")
|
|
{
|
|
// You shouldn't be picking up BasicArmor anyway.
|
|
return true;
|
|
}
|
|
if (!item.bIgnoreSkill)
|
|
{
|
|
if (item is "BasicArmorBonus")
|
|
{
|
|
let armor = BasicArmorBonus(item);
|
|
armor.SaveAmount = int(armor.SaveAmount * G_SkillPropertyFloat(SKILLP_ArmorFactor));
|
|
}
|
|
else if (item is "BasicArmorPickup")
|
|
{
|
|
let armor = BasicArmorPickup(item);
|
|
armor.SaveAmount = int(armor.SaveAmount * G_SkillPropertyFloat(SKILLP_ArmorFactor));
|
|
}
|
|
}
|
|
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
|
|
int MaxSaveAmount;
|
|
int MaxAbsorb;
|
|
int MaxFullAbsorb;
|
|
int SaveAmount;
|
|
int BonusCount;
|
|
int BonusMax;
|
|
|
|
property prefix: Armor;
|
|
property MaxSaveAmount: MaxSaveAmount;
|
|
property SaveAmount : SaveAmount;
|
|
property SavePercent: SavePercent;
|
|
property MaxAbsorb: MaxAbsorb;
|
|
property MaxFullAbsorb: MaxFullAbsorb;
|
|
property MaxBonus: BonusCount;
|
|
property MaxBonusMax: BonusMax;
|
|
|
|
Default
|
|
{
|
|
+Inventory.AUTOACTIVATE
|
|
+Inventory.ALWAYSPICKUP
|
|
Inventory.MaxAmount 0;
|
|
Armor.SavePercent 33.335;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// ABasicArmorBonus :: CreateCopy
|
|
//
|
|
//===========================================================================
|
|
|
|
override Inventory CreateCopy (Actor other)
|
|
{
|
|
let copy = BasicArmorBonus(Super.CreateCopy (other));
|
|
|
|
if (!bIgnoreSkill)
|
|
{
|
|
SaveAmount = int(SaveAmount * G_SkillPropertyFloat(SKILLP_ArmorFactor));
|
|
}
|
|
|
|
copy.SavePercent = SavePercent;
|
|
copy.SaveAmount = SaveAmount;
|
|
copy.MaxSaveAmount = MaxSaveAmount;
|
|
copy.BonusCount = BonusCount;
|
|
copy.BonusMax = BonusMax;
|
|
copy.MaxAbsorb = MaxAbsorb;
|
|
copy.MaxFullAbsorb = MaxFullAbsorb;
|
|
|
|
return copy;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// ABasicArmorBonus :: Use
|
|
//
|
|
// Tries to add to the amount of BasicArmor a player has.
|
|
//
|
|
//===========================================================================
|
|
|
|
override bool Use (bool pickup)
|
|
{
|
|
let armor = BasicArmor(Owner.FindInventory("BasicArmor"));
|
|
bool result = false;
|
|
|
|
// This should really never happen but let's be prepared for a broken inventory.
|
|
if (armor == null)
|
|
{
|
|
armor = BasicArmor(Spawn("BasicArmor"));
|
|
armor.BecomeItem ();
|
|
armor.Amount = 0;
|
|
armor.MaxAmount = MaxSaveAmount;
|
|
Owner.AddInventory (armor);
|
|
}
|
|
|
|
if (BonusCount > 0 && armor.BonusCount < BonusMax)
|
|
{
|
|
armor.BonusCount = min(armor.BonusCount + BonusCount, BonusMax);
|
|
result = true;
|
|
}
|
|
|
|
int saveAmount = min(SaveAmount, MaxSaveAmount);
|
|
|
|
if (saveAmount <= 0)
|
|
{ // If it can't give you anything, it's as good as used.
|
|
return BonusCount > 0 ? result : true;
|
|
}
|
|
|
|
// If you already have more armor than this item can give you, you can't
|
|
// use it.
|
|
if (armor.Amount >= MaxSaveAmount + armor.BonusCount)
|
|
{
|
|
return result;
|
|
}
|
|
|
|
if (armor.Amount <= 0)
|
|
{ // Should never be less than 0, but might as well check anyway
|
|
armor.Amount = 0;
|
|
armor.Icon = Icon;
|
|
armor.SavePercent = clamp(SavePercent, 0, 100) / 100;
|
|
armor.MaxAbsorb = MaxAbsorb;
|
|
armor.ArmorType = GetClassName();
|
|
armor.MaxFullAbsorb = MaxFullAbsorb;
|
|
armor.ActualSaveAmount = MaxSaveAmount;
|
|
}
|
|
|
|
armor.Amount = min(armor.Amount + saveAmount, MaxSaveAmount + armor.BonusCount);
|
|
armor.MaxAmount = max(armor.MaxAmount, MaxSaveAmount);
|
|
return true;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
//
|
|
// BasicArmorPickup
|
|
//
|
|
//
|
|
//===========================================================================
|
|
|
|
class BasicArmorPickup : Armor
|
|
{
|
|
|
|
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
|
|
//
|
|
// Hexen armor consists of four separate armor types plus a conceptual armor
|
|
// type (the player himself) that work together as a single armor.
|
|
//
|
|
//
|
|
//===========================================================================
|
|
|
|
class HexenArmor : Armor
|
|
{
|
|
|
|
double Slots[5];
|
|
double SlotsIncrement[4];
|
|
|
|
Default
|
|
{
|
|
+Inventory.KEEPDEPLETED
|
|
+Inventory.UNTOSSABLE
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// AHexenArmor :: CreateCopy
|
|
//
|
|
//===========================================================================
|
|
|
|
override Inventory CreateCopy (Actor other)
|
|
{
|
|
// Like BasicArmor, HexenArmor is used in the inventory but not the map.
|
|
// health is the slot this armor occupies.
|
|
// Amount is the quantity to give (0 = normal max).
|
|
let copy = HexenArmor(Spawn("HexenArmor"));
|
|
copy.AddArmorToSlot (health, Amount);
|
|
GoAwayAndDie ();
|
|
return copy;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// AHexenArmor :: CreateTossable
|
|
//
|
|
// Since this isn't really a single item, you can't drop it. Ever.
|
|
//
|
|
//===========================================================================
|
|
|
|
override Inventory CreateTossable (int amount)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// AHexenArmor :: HandlePickup
|
|
//
|
|
//===========================================================================
|
|
|
|
override bool HandlePickup (Inventory item)
|
|
{
|
|
if (item is "HexenArmor")
|
|
{
|
|
if (AddArmorToSlot (item.health, item.Amount))
|
|
{
|
|
item.bPickupGood = true;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// AHexenArmor :: AddArmorToSlot
|
|
//
|
|
//===========================================================================
|
|
|
|
protected bool AddArmorToSlot (int slot, int amount)
|
|
{
|
|
double hits;
|
|
|
|
if (slot < 0 || slot > 3)
|
|
{
|
|
return false;
|
|
}
|
|
if (amount <= 0)
|
|
{
|
|
hits = SlotsIncrement[slot];
|
|
if (Slots[slot] < hits)
|
|
{
|
|
Slots[slot] = hits;
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hits = amount * 5;
|
|
let total = Slots[0] + Slots[1] + Slots[2] + Slots[3] + Slots[4];
|
|
let max = SlotsIncrement[0] + SlotsIncrement[1] + SlotsIncrement[2] + SlotsIncrement[3] + Slots[4] + 4 * 5;
|
|
if (total < max)
|
|
{
|
|
Slots[slot] += hits;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// AHexenArmor :: AbsorbDamage
|
|
//
|
|
//===========================================================================
|
|
|
|
override void AbsorbDamage (int damage, Name damageType, out int newdamage)
|
|
{
|
|
if (!DamageTypeDefinition.IgnoreArmor(damageType))
|
|
{
|
|
double savedPercent = Slots[0] + Slots[1] + Slots[2] + Slots[3] + Slots[4];
|
|
|
|
if (savedPercent)
|
|
{ // armor absorbed some damage
|
|
if (savedPercent > 100)
|
|
{
|
|
savedPercent = 100;
|
|
}
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
if (Slots[i])
|
|
{
|
|
// 300 damage always wipes out the armor unless some was added
|
|
// with the dragon skin bracers.
|
|
if (damage < 10000)
|
|
{
|
|
Slots[i] -= damage * SlotsIncrement[i] / 300.;
|
|
if (Slots[i] < 2)
|
|
{
|
|
Slots[i] = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Slots[i] = 0;
|
|
}
|
|
}
|
|
}
|
|
int saved = int(damage * savedPercent / 100.);
|
|
if (saved > savedPercent*2)
|
|
{
|
|
saved = int(savedPercent*2);
|
|
}
|
|
newdamage -= saved;
|
|
damage = newdamage;
|
|
}
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// AHexenArmor :: DepleteOrDestroy
|
|
//
|
|
//===========================================================================
|
|
|
|
override void DepleteOrDestroy()
|
|
{
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
Slots[i] = 0;
|
|
}
|
|
}
|
|
}
|
|
|