fix: Fixes splitting potions before error (#1862)

### Summary

Most potions have various checks, such as a cooldown. This fixes an edge case where potions get split from their stack before erroring.
This commit is contained in:
Kamron Batman 2024-07-06 16:39:47 -07:00 committed by GitHub
parent e6bfc45228
commit 38731d98b6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 169 additions and 99 deletions

View file

@ -22,13 +22,7 @@ public partial class MelisandesFermentedWine : GreaterExplosionPotion
public override int LabelNumber => 1072114; // Melisande's Fermented Wine
public override void Drink(Mobile from)
{
if (MondainsLegacy.CheckML(from))
{
base.Drink(from);
}
}
public override bool CanDrink(Mobile from) => MondainsLegacy.CheckML(from) && base.CanDrink(from);
public override void GetProperties(IPropertyList list)
{

View file

@ -14,25 +14,27 @@ public abstract partial class BaseAgilityPotion : BasePotion
public abstract int DexOffset { get; }
public abstract TimeSpan Duration { get; }
public bool DoAgility(Mobile from)
public override bool CanDrink(Mobile from)
{
// TODO: Verify scaled; is it offset, duration, or both?
if (SpellHelper.AddStatOffset(from, StatType.Dex, Scale(from, DexOffset), Duration))
if (!base.CanDrink(from))
{
from.FixedEffect(0x375A, 10, 15);
from.PlaySound(0x1E7);
return true;
return false;
}
from.SendLocalizedMessage(502173); // You are already under a similar effect.
return false;
// TODO: Verify scaled; is it offset, duration, or both?
if (!SpellHelper.AddStatOffset(from, StatType.Dex, Scale(from, DexOffset), Duration))
{
from.SendLocalizedMessage(502173); // You are already under a similar effect.
return false;
}
from.FixedEffect(0x375A, 10, 15);
from.PlaySound(0x1E7);
return true;
}
public override void Drink(Mobile from)
{
if (DoAgility(from))
{
PlayDrinkEffect(from);
}
PlayDrinkEffect(from);
}
}

View file

@ -129,23 +129,11 @@ public abstract partial class BasePotion : Item, ICraftable, ICommodity
public override void OnDoubleClick(Mobile from)
{
if (!Movable)
if (!CanDrink(from))
{
return;
}
if (!from.InRange(GetWorldLocation(), 1))
{
from.SendLocalizedMessage(502138); // That is too far away for you to use
return;
}
if (RequireFreeHand && !HasFreeHand(from))
{
from.SendLocalizedMessage(502172); // You must have a free hand to drink a potion.
return;
}
var pot = this;
if (IsThrowablePotion && Amount > 1)
@ -172,12 +160,33 @@ public abstract partial class BasePotion : Item, ICraftable, ICommodity
_potionEffect = (PotionEffect)reader.ReadInt();
}
public virtual bool CanDrink(Mobile from)
{
if (!Movable)
{
return false;
}
if (!from.InRange(GetWorldLocation(), 1))
{
from.SendLocalizedMessage(502138); // That is too far away for you to use
return false;
}
if (RequireFreeHand && !HasFreeHand(from))
{
from.SendLocalizedMessage(502172); // You must have a free hand to drink a potion.
return false;
}
return true;
}
public abstract void Drink(Mobile from);
public void PlayDrinkEffect(Mobile m)
{
m.RevealingAction();
m.PlaySound(0x2D6);
if (!DuelContext.IsFreeConsume(m))

View file

@ -22,12 +22,17 @@ public abstract partial class BaseConflagrationPotion : BasePotion
public override bool IsThrowablePotion => true;
public override void Drink(Mobile from)
public override bool CanDrink(Mobile from)
{
if (!base.CanDrink(from))
{
return false;
}
if (Core.AOS && (from.Paralyzed || from.Frozen || from.Spell?.IsCasting == true))
{
from.SendLocalizedMessage(1062725); // You can not use that potion while paralyzed.
return;
return false;
}
var delay = GetDelay(from);
@ -36,14 +41,14 @@ public abstract partial class BaseConflagrationPotion : BasePotion
{
// You cannot use that for another ~1_NUM~ ~2_TIMEUNITS~
from.SendLocalizedMessage(1072529, $"{delay}\t{(delay > 1 ? "seconds." : "second.")}");
return;
return false;
}
if ((from.Target as ThrowTarget)?.Potion == this)
{
return;
}
return (from.Target as ThrowTarget)?.Potion != this;
}
public override void Drink(Mobile from)
{
from.RevealingAction();
_users ??= [];

View file

@ -23,12 +23,17 @@ public abstract partial class BaseConfusionBlastPotion : BasePotion
public override bool IsThrowablePotion => true;
public override void Drink(Mobile from)
public override bool CanDrink(Mobile from)
{
if (!base.CanDrink(from))
{
return false;
}
if (Core.AOS && (from.Paralyzed || from.Frozen || from.Spell?.IsCasting == true))
{
from.SendLocalizedMessage(1062725); // You can not use that potion while paralyzed.
return;
return false;
}
var delay = GetDelay(from);
@ -37,14 +42,14 @@ public abstract partial class BaseConfusionBlastPotion : BasePotion
{
// You cannot use that for another ~1_NUM~ ~2_TIMEUNITS~
from.SendLocalizedMessage(1072529, $"{delay}\t{(delay > 1 ? "seconds." : "second.")}");
return;
return false;
}
if ((from.Target as ThrowTarget)?.Potion == this)
{
return;
}
return (from.Target as ThrowTarget)?.Potion != this;
}
public override void Drink(Mobile from)
{
from.RevealingAction();
_users ??= [];

View file

@ -56,24 +56,35 @@ public abstract partial class BaseCurePotion : BasePotion
}
}
public override void Drink(Mobile from)
public override bool CanDrink(Mobile from)
{
if (!base.CanDrink(from))
{
return false;
}
if (TransformationSpellHelper.UnderTransformation(from, typeof(VampiricEmbraceSpell)))
{
from.SendLocalizedMessage(1061652); // The garlic in the potion would surely kill you.
return false;
}
else if (from.Poisoned)
{
DoCure(from);
PlayDrinkEffect(from);
from.FixedParticles(0x373A, 10, 15, 5012, EffectLayer.Waist);
from.PlaySound(0x1E0);
}
else
if (!from.Poisoned)
{
from.SendLocalizedMessage(1042000); // You are not poisoned.
return false;
}
return true;
}
public override void Drink(Mobile from)
{
DoCure(from);
PlayDrinkEffect(from);
from.FixedParticles(0x373A, 10, 15, 5012, EffectLayer.Waist);
from.PlaySound(0x1E0);
}
}

View file

@ -51,22 +51,26 @@ public abstract partial class BaseExplosionPotion : BasePotion
return this;
}
public override void Drink(Mobile from)
public override bool CanDrink(Mobile from)
{
if (!base.CanDrink(from))
{
return false;
}
if (Core.AOS && (from.Paralyzed || from.Frozen || from.Spell?.IsCasting == true))
{
from.SendLocalizedMessage(1062725); // You can not use a purple potion while paralyzed.
return;
return false;
}
var targ = from.Target as ThrowTarget;
return (from.Target as ThrowTarget)?.Potion != this;
}
public override void Drink(Mobile from)
{
Stackable = false; // Scavenged explosion potions won't stack with those ones in backpack, and still will explode.
if (targ?.Potion == this)
{
return;
}
from.RevealingAction();
_users ??= [];

View file

@ -22,29 +22,39 @@ public abstract partial class BaseHealPotion : BasePotion
from.Heal(Utility.RandomMinMax(min, max));
}
public override void Drink(Mobile from)
public override bool CanDrink(Mobile from)
{
if (!base.CanDrink(from))
{
return false;
}
if (from.Hits >= from.HitsMax)
{
// You decide against drinking this potion, as you are already at full health.
from.SendLocalizedMessage(1049547);
return;
return false;
}
if (from.Poisoned || MortalStrike.IsWounded(from))
{
// You can not heal yourself in your current state.
from.LocalOverheadMessage(MessageType.Regular, 0x22, 1005000);
return;
return false;
}
if (!from.BeginAction<BaseHealPotion>())
{
// You must wait 10 seconds before using another healing potion.
from.LocalOverheadMessage(MessageType.Regular, 0x22, 500235);
return;
return false;
}
return true;
}
public override void Drink(Mobile from)
{
DoHeal(from);
PlayDrinkEffect(from);

View file

@ -7,29 +7,39 @@ namespace Server.Items;
[SerializationGenerator(0, false)]
public partial class InvisibilityPotion : BasePotion
{
private static readonly Dictionary<Mobile, TimerExecutionToken> m_Table = new();
private static readonly Dictionary<Mobile, TimerExecutionToken> _table = new();
[Constructible]
public InvisibilityPotion() : base(0xF0A, PotionEffect.Invisibility) => Hue = 0x48D;
public override int LabelNumber => 1072941; // Potion of Invisibility
public override void Drink(Mobile from)
public override bool CanDrink(Mobile from)
{
if (!base.CanDrink(from))
{
return false;
}
if (from.Hidden)
{
from.SendLocalizedMessage(1073185); // You are already unseen.
return;
return false;
}
if (HasTimer(from))
{
from.SendLocalizedMessage(1073186); // An invisibility potion is already taking effect on your person.
return;
return false;
}
return true;
}
public override void Drink(Mobile from)
{
Timer.StartTimer(TimeSpan.FromSeconds(2), () => Hide(from), out var timerToken);
m_Table[from] = timerToken;
_table[from] = timerToken;
PlayDrinkEffect(from);
}
@ -61,11 +71,11 @@ public partial class InvisibilityPotion : BasePotion
RemoveTimer(m);
}
public static bool HasTimer(Mobile m) => m_Table.ContainsKey(m);
public static bool HasTimer(Mobile m) => _table.ContainsKey(m);
public static void RemoveTimer(Mobile m, bool interrupted = false)
{
if (m_Table.Remove(m, out var timer))
if (_table.Remove(m, out var timer))
{
if (interrupted)
{

View file

@ -10,21 +10,30 @@ public partial class NightSightPotion : BasePotion
{
}
public override bool CanDrink(Mobile from)
{
if (!base.CanDrink(from))
{
return false;
}
if (!from.BeginAction<LightCycle>())
{
from.SendMessage("You already have night sight.");
return false;
}
return true;
}
public override void Drink(Mobile from)
{
if (from.BeginAction<LightCycle>())
{
new LightCycle.NightSightTimer(from).Start();
from.LightLevel = LightCycle.DungeonLevel / 2;
new LightCycle.NightSightTimer(from).Start();
from.LightLevel = LightCycle.DungeonLevel / 2;
from.FixedParticles(0x376A, 9, 32, 5007, EffectLayer.Waist);
from.PlaySound(0x1E3);
from.FixedParticles(0x376A, 9, 32, 5007, EffectLayer.Waist);
from.PlaySound(0x1E3);
PlayDrinkEffect(from);
}
else
{
from.SendMessage("You already have nightsight.");
}
PlayDrinkEffect(from);
}
}

View file

@ -11,17 +11,26 @@ public abstract partial class BaseRefreshPotion : BasePotion
public abstract double Refresh { get; }
public override void Drink(Mobile from)
public override bool CanDrink(Mobile from)
{
if (from.Stam < from.StamMax)
if (!base.CanDrink(from))
{
from.Stam += Scale(from, (int)(Refresh * from.StamMax));
PlayDrinkEffect(from);
return false;
}
else
if (from.Stam >= from.StamMax)
{
from.SendMessage("You decide against drinking this potion, as you are already at full stamina.");
return false;
}
return true;
}
public override void Drink(Mobile from)
{
from.Stam += Scale(from, (int)(Refresh * from.StamMax));
PlayDrinkEffect(from);
}
}

View file

@ -14,8 +14,13 @@ public abstract partial class BaseStrengthPotion : BasePotion
public abstract int StrOffset { get; }
public abstract TimeSpan Duration { get; }
public bool DoStrength(Mobile from)
public bool CanDrink(Mobile from)
{
if (!base.CanDrink(from))
{
return false;
}
// TODO: Verify scaled; is it offset, duration, or both?
if (SpellHelper.AddStatOffset(from, StatType.Str, Scale(from, StrOffset), Duration))
{
@ -30,9 +35,6 @@ public abstract partial class BaseStrengthPotion : BasePotion
public override void Drink(Mobile from)
{
if (DoStrength(from))
{
PlayDrinkEffect(from);
}
PlayDrinkEffect(from);
}
}