mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-12-17 07:51:06 +00:00
eb47fb9adc
Now a child type can decide for itself how to treat 'amount'. The scripting interfaces to this function in ACS and FraggleScript have been consolidated and also scriptified.
627 lines
16 KiB
Text
627 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.
|
|
{
|
|
newdamage = 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;
|
|
}
|
|
|
|
|
|
override void SetGiveAmount(Actor receiver, int amount, bool bycheat)
|
|
{
|
|
SaveAmount *= amount;
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
//
|
|
// 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;
|
|
}
|
|
|
|
override void SetGiveAmount(Actor receiver, int amount, bool bycheat)
|
|
{
|
|
SaveAmount *= amount;
|
|
}
|
|
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
//
|
|
// 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;
|
|
}
|
|
}
|
|
}
|
|
|