feat: implement Healing Stone spell
This commit is contained in:
parent
fa3b9024cb
commit
b048bc9813
6 changed files with 726 additions and 2 deletions
|
|
@ -0,0 +1,323 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Server.Items;
|
||||
using Server.Spells;
|
||||
using Server.Spells.Mysticism;
|
||||
using Xunit;
|
||||
|
||||
namespace Server.Tests.Spells.Mysticism;
|
||||
|
||||
[Collection("Sequential UOContent Tests")]
|
||||
public class HealingStoneTests
|
||||
{
|
||||
[Fact]
|
||||
public void SpellMetadata_MatchesHealingStoneSources()
|
||||
{
|
||||
var caster = NewMysticCaster();
|
||||
var spell = new HealingStoneSpell(caster);
|
||||
|
||||
Assert.Equal("Healing Stone", spell.Name);
|
||||
Assert.Equal("Kal In Mani", spell.Mantra);
|
||||
Assert.Equal(SpellCircle.First, spell.Circle);
|
||||
Assert.Equal(TimeSpan.FromSeconds(5.0), spell.CastDelayBase);
|
||||
Assert.Equal(4, spell.GetMana());
|
||||
Assert.Equal(0.0, spell.RequiredSkill);
|
||||
Assert.Equal(SkillName.Mysticism, spell.CastSkill);
|
||||
Assert.Equal([Reagent.Bone, Reagent.Garlic, Reagent.Ginseng, Reagent.SpidersSilk], spell.Reagents);
|
||||
|
||||
caster.Delete();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MysticSpellbookAndScroll_UseHealingStoneSlot()
|
||||
{
|
||||
var book = new MysticSpellbook(1UL << (678 - 677));
|
||||
var scroll = new HealingStoneScroll();
|
||||
|
||||
Assert.Equal(677, book.BookOffset);
|
||||
Assert.Equal(16, book.BookCount);
|
||||
Assert.True(book.HasSpell(678));
|
||||
Assert.Equal(678, GetSpellScrollId(scroll));
|
||||
|
||||
book.Delete();
|
||||
scroll.Delete();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RegisterMysticism_PreSa_DoesNotExposeHealingStone()
|
||||
{
|
||||
var previousExpansion = Core.Expansion;
|
||||
var caster = NewMysticCaster();
|
||||
|
||||
try
|
||||
{
|
||||
ResetSpellRegistry();
|
||||
Core.Expansion = Expansion.ML;
|
||||
Initializer.Configure();
|
||||
|
||||
Assert.Null(SpellRegistry.NewSpell(678, caster, null));
|
||||
Assert.Equal(-1, SpellRegistry.GetRegistryNumber(typeof(HealingStoneSpell)));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Core.Expansion = previousExpansion;
|
||||
ResetSpellRegistry();
|
||||
Initializer.Configure();
|
||||
caster.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RegisterMysticism_Sa_ExposesHealingStoneAtSpellId678()
|
||||
{
|
||||
var previousExpansion = Core.Expansion;
|
||||
var caster = NewMysticCaster();
|
||||
|
||||
try
|
||||
{
|
||||
ResetSpellRegistry();
|
||||
Core.Expansion = Expansion.SA;
|
||||
Initializer.Configure();
|
||||
|
||||
Assert.Same(typeof(HealingStoneSpell), SpellRegistry.Types[678]);
|
||||
Assert.Equal(678, SpellRegistry.GetRegistryNumber(typeof(HealingStoneSpell)));
|
||||
Assert.IsType<HealingStoneSpell>(SpellRegistry.NewSpell(678, caster, null));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Core.Expansion = previousExpansion;
|
||||
ResetSpellRegistry();
|
||||
Initializer.Configure();
|
||||
caster.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CastingHealingStone_ConsumesResourcesAndCreatesOneOwnedStone()
|
||||
{
|
||||
var previousExpansion = Core.Expansion;
|
||||
var caster = NewMysticCaster();
|
||||
AddReagents(caster);
|
||||
|
||||
try
|
||||
{
|
||||
Core.Expansion = Expansion.SA;
|
||||
var spell = new TestHealingStoneSpell(caster);
|
||||
caster.Spell = spell;
|
||||
spell.State = SpellState.Sequencing;
|
||||
|
||||
spell.OnCast();
|
||||
|
||||
var stone = caster.Backpack.FindItemByType<HealingStone>();
|
||||
Assert.NotNull(stone);
|
||||
Assert.Same(caster, stone.Owner);
|
||||
Assert.Equal(96, caster.Mana);
|
||||
Assert.Equal(9, caster.Backpack.FindItemByType<Bone>().Amount);
|
||||
Assert.Equal(9, caster.Backpack.FindItemByType<Garlic>().Amount);
|
||||
Assert.Equal(9, caster.Backpack.FindItemByType<Ginseng>().Amount);
|
||||
Assert.Equal(9, caster.Backpack.FindItemByType<SpidersSilk>().Amount);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Core.Expansion = previousExpansion;
|
||||
caster.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HealingStone_HealsOnlyOwnerAndRequiresBackpack()
|
||||
{
|
||||
var owner = NewMobile();
|
||||
var other = NewMobile();
|
||||
var stone = new HealingStone(owner, 100, 20);
|
||||
owner.AddToBackpack(stone);
|
||||
owner.Hits = owner.HitsMax - 20;
|
||||
|
||||
try
|
||||
{
|
||||
Assert.False(stone.TryUseForTests(other));
|
||||
Assert.Equal(100, stone.LifeForce);
|
||||
|
||||
owner.Hits = owner.HitsMax;
|
||||
Assert.False(stone.TryUseForTests(owner));
|
||||
Assert.Equal(100, stone.LifeForce);
|
||||
|
||||
owner.Hits = owner.HitsMax - 20;
|
||||
Assert.True(stone.TryUseForTests(owner));
|
||||
Assert.Equal(owner.HitsMax, owner.Hits);
|
||||
Assert.Equal(80, stone.LifeForce);
|
||||
Assert.Equal(1, stone.AvailableHealing);
|
||||
|
||||
owner.EndAction<HealingStone>();
|
||||
stone.MoveToWorld(owner.Location, owner.Map);
|
||||
Assert.False(stone.TryUseForTests(owner));
|
||||
}
|
||||
finally
|
||||
{
|
||||
owner.Delete();
|
||||
other.Delete();
|
||||
stone.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HealingStone_RechargesOneUseOverFifteenSeconds()
|
||||
{
|
||||
var previousNow = Core.Now;
|
||||
var owner = NewMobile();
|
||||
var stone = new HealingStone(owner, 100, 30);
|
||||
owner.AddToBackpack(stone);
|
||||
owner.Hits = owner.HitsMax - 30;
|
||||
|
||||
try
|
||||
{
|
||||
Assert.True(stone.TryUseForTests(owner));
|
||||
owner.EndAction<HealingStone>();
|
||||
Assert.Equal(1, stone.AvailableHealing);
|
||||
|
||||
Core._now = previousNow.AddSeconds(HealingStone.FullRechargeSeconds);
|
||||
Assert.Equal(stone.MaxHealing, stone.AvailableHealing);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Core._now = previousNow;
|
||||
owner.Delete();
|
||||
stone.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HealingStone_RejectsSecureTradeAndDeletesWhenDropped()
|
||||
{
|
||||
var owner = NewMobile();
|
||||
var other = NewMobile();
|
||||
var stone = new HealingStone(owner, 100, 20);
|
||||
owner.AddToBackpack(stone);
|
||||
|
||||
Assert.False(stone.AllowSecureTrade(owner, other, owner, true));
|
||||
Assert.False(stone.DropToWorld(owner, Point3D.Zero));
|
||||
Assert.True(stone.Deleted);
|
||||
|
||||
owner.Delete();
|
||||
other.Delete();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HealingStone_CureCostAndChanceScaleByPoisonLevel()
|
||||
{
|
||||
Assert.Equal(0, HealingStone.GetCureCost(0));
|
||||
Assert.Equal(25, HealingStone.GetCureCost(1));
|
||||
Assert.Equal(100, HealingStone.GetCureCost(4));
|
||||
Assert.Equal(120, HealingStone.GetCureCost(5));
|
||||
Assert.Equal(170.0, HealingStone.GetCureChance(120.0, 120.0, 0));
|
||||
Assert.Equal(90.0, HealingStone.GetCureChance(120.0, 120.0, 4));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HealingPotion_ResetsStonePerUseHealing()
|
||||
{
|
||||
var previousNow = Core.Now;
|
||||
var owner = NewMobile();
|
||||
var stone = new HealingStone(owner, 100, 20);
|
||||
owner.AddToBackpack(stone);
|
||||
owner.Hits = owner.HitsMax - 20;
|
||||
|
||||
Assert.True(stone.TryUseForTests(owner));
|
||||
owner.EndAction<HealingStone>();
|
||||
Assert.Equal(1, stone.AvailableHealing);
|
||||
|
||||
Core._now = previousNow.AddSeconds(HealingStone.FullRechargeSeconds);
|
||||
Assert.Equal(stone.MaxHealing, stone.AvailableHealing);
|
||||
|
||||
var potion = new TestHealPotion();
|
||||
owner.Hits = owner.HitsMax - 10;
|
||||
potion.DoHeal(owner);
|
||||
|
||||
Assert.Equal(1, stone.AvailableHealing);
|
||||
|
||||
potion.Delete();
|
||||
owner.Delete();
|
||||
stone.Delete();
|
||||
Core._now = previousNow;
|
||||
}
|
||||
|
||||
private static Mobile NewMobile(bool withBackpack = true)
|
||||
{
|
||||
var mobile = new Mobile(World.NewMobile);
|
||||
mobile.DefaultMobileInit();
|
||||
mobile.Player = true;
|
||||
mobile.InitStats(100, 100, 100);
|
||||
mobile.Hits = mobile.HitsMax;
|
||||
mobile.Mana = mobile.ManaMax;
|
||||
|
||||
if (withBackpack)
|
||||
{
|
||||
mobile.AddItem(new Backpack());
|
||||
}
|
||||
|
||||
return mobile;
|
||||
}
|
||||
|
||||
private static Mobile NewMysticCaster()
|
||||
{
|
||||
var caster = NewMobile();
|
||||
caster.Skills.Mysticism.Base = 120.0;
|
||||
caster.Skills.Focus.Base = 120.0;
|
||||
caster.Skills.Imbuing.Base = 0.0;
|
||||
return caster;
|
||||
}
|
||||
|
||||
private static void AddReagents(Mobile caster)
|
||||
{
|
||||
caster.AddToBackpack(new Bone(10));
|
||||
caster.AddToBackpack(new Garlic(10));
|
||||
caster.AddToBackpack(new Ginseng(10));
|
||||
caster.AddToBackpack(new SpidersSilk(10));
|
||||
}
|
||||
|
||||
private static int GetSpellScrollId(SpellScroll scroll)
|
||||
{
|
||||
var field = typeof(SpellScroll).GetField("_spellID", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
return (int)field!.GetValue(scroll)!;
|
||||
}
|
||||
|
||||
private static void ResetSpellRegistry()
|
||||
{
|
||||
var types = (Type[])typeof(SpellRegistry)
|
||||
.GetField("m_Types", BindingFlags.Static | BindingFlags.NonPublic)!
|
||||
.GetValue(null)!;
|
||||
Array.Clear(types);
|
||||
|
||||
var idsFromTypes = (Dictionary<Type, int>)typeof(SpellRegistry)
|
||||
.GetField("m_IDsFromTypes", BindingFlags.Static | BindingFlags.NonPublic)!
|
||||
.GetValue(null)!;
|
||||
idsFromTypes.Clear();
|
||||
|
||||
typeof(SpellRegistry)
|
||||
.GetField("m_Count", BindingFlags.Static | BindingFlags.NonPublic)!
|
||||
.SetValue(null, 0);
|
||||
|
||||
SpellRegistry.SpecialMoves.Clear();
|
||||
}
|
||||
|
||||
private sealed class TestHealingStoneSpell : HealingStoneSpell
|
||||
{
|
||||
public TestHealingStoneSpell(Mobile caster) : base(caster)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool CheckFizzle() => true;
|
||||
}
|
||||
|
||||
private sealed class TestHealPotion : BaseHealPotion
|
||||
{
|
||||
public TestHealPotion() : base(PotionEffect.Heal)
|
||||
{
|
||||
}
|
||||
|
||||
public override int MinHeal => 10;
|
||||
public override int MaxHeal => 10;
|
||||
public override double Delay => 10.0;
|
||||
}
|
||||
}
|
||||
286
Projects/UOContent/Items/Skill Items/Magical/HealingStone.cs
Normal file
286
Projects/UOContent/Items/Skill Items/Magical/HealingStone.cs
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
using System;
|
||||
using ModernUO.Serialization;
|
||||
using Server.Engines.ConPVP;
|
||||
using Server.Spells;
|
||||
using Server.Spells.Mysticism;
|
||||
|
||||
namespace Server.Items;
|
||||
|
||||
[SerializationGenerator(0)]
|
||||
public partial class HealingStone : Item
|
||||
{
|
||||
public const int ItemID = 0x4078;
|
||||
public const int FullRechargeSeconds = 15;
|
||||
public const int UseCooldownSeconds = 2;
|
||||
|
||||
[SerializableField(0)]
|
||||
private Mobile _ownerMobile;
|
||||
|
||||
[SerializableField(1)]
|
||||
[InvalidateProperties]
|
||||
private int _storedLifeForce;
|
||||
|
||||
[SerializableField(2)]
|
||||
private int _maximumLifeForce;
|
||||
|
||||
[SerializableField(3)]
|
||||
[InvalidateProperties]
|
||||
private int _healingAvailable;
|
||||
|
||||
[SerializableField(4)]
|
||||
private int _maximumHealing;
|
||||
|
||||
[SerializableField(5)]
|
||||
private DateTime _rechargeStarted;
|
||||
|
||||
[Constructible]
|
||||
public HealingStone(Mobile owner, int lifeForce, int maxHealing) : base(ItemID)
|
||||
{
|
||||
_ownerMobile = owner;
|
||||
_maximumLifeForce = Math.Max(0, lifeForce);
|
||||
_storedLifeForce = _maximumLifeForce;
|
||||
_maximumHealing = Math.Max(1, maxHealing);
|
||||
_healingAvailable = _maximumHealing;
|
||||
_rechargeStarted = Core.Now;
|
||||
|
||||
LootType = LootType.Blessed;
|
||||
}
|
||||
|
||||
public override double DefaultWeight => 1.0;
|
||||
|
||||
public override string DefaultName => "a healing stone";
|
||||
|
||||
public override bool Nontransferable => true;
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public Mobile Owner => _ownerMobile;
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int LifeForce
|
||||
{
|
||||
get => _storedLifeForce;
|
||||
set
|
||||
{
|
||||
_storedLifeForce = Math.Clamp(value, 0, _maximumLifeForce);
|
||||
InvalidateProperties();
|
||||
|
||||
if (_storedLifeForce <= 0)
|
||||
{
|
||||
Delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int MaxLifeForce => _maximumLifeForce;
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int MaxHealing => _maximumHealing;
|
||||
|
||||
[CommandProperty(AccessLevel.GameMaster)]
|
||||
public int AvailableHealing
|
||||
{
|
||||
get
|
||||
{
|
||||
Replenish();
|
||||
return _healingAvailable;
|
||||
}
|
||||
}
|
||||
|
||||
public override void GetProperties(IPropertyList list)
|
||||
{
|
||||
base.GetProperties(list);
|
||||
list.Add(1115274, _storedLifeForce);
|
||||
}
|
||||
|
||||
public override void OnDoubleClick(Mobile from) => TryUse(from);
|
||||
|
||||
internal bool TryUseForTests(Mobile from) => TryUse(from);
|
||||
|
||||
public override bool AllowSecureTrade(Mobile from, Mobile to, Mobile newOwner, bool accepted) => false;
|
||||
|
||||
public override bool DropToWorld(Mobile from, Point3D p)
|
||||
{
|
||||
Delete();
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void OnAfterDelete()
|
||||
{
|
||||
_ownerMobile = null;
|
||||
base.OnAfterDelete();
|
||||
}
|
||||
|
||||
public static int GetCureCost(int poisonLevel) => Math.Min(120, Math.Max(0, poisonLevel * 25));
|
||||
|
||||
public static double GetCureChance(double mysticism, double supportSkill, int poisonLevel)
|
||||
{
|
||||
var effectiveSkill = (mysticism + supportSkill) / 2.0;
|
||||
return (10000 + effectiveSkill * 75 - (poisonLevel + 1) * 2000) / 100.0;
|
||||
}
|
||||
|
||||
internal static void OnPotionHealed(Mobile from)
|
||||
{
|
||||
var stone = from?.Backpack?.FindItemByType<HealingStone>();
|
||||
stone?.ResetHealingAfterPotion();
|
||||
}
|
||||
|
||||
private bool TryUse(Mobile from)
|
||||
{
|
||||
if (Deleted || from == null || from.Deleted)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (from != _ownerMobile)
|
||||
{
|
||||
from.SendMessage("Only the mystic who created this stone can use it.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsChildOf(from.Backpack))
|
||||
{
|
||||
from.SendLocalizedMessage(1042001); // That must be in your pack for you to use it.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!from.InRange(GetWorldLocation(), 1))
|
||||
{
|
||||
from.SendLocalizedMessage(502138); // That is too far away for you to use.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!BasePotion.HasFreeHand(from))
|
||||
{
|
||||
from.SendLocalizedMessage(1080116); // You must have a free hand to use a Healing Stone.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!from.Poisoned && from.Hits >= from.HitsMax)
|
||||
{
|
||||
from.SendLocalizedMessage(1049547); // You are already at full health.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (MortalStrike.IsWounded(from))
|
||||
{
|
||||
from.LocalOverheadMessage(MessageType.Regular, 0x22, 1005000); // You can not heal yourself in your current state.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!from.CanBeginAction<HealingStone>())
|
||||
{
|
||||
from.SendLocalizedMessage(1095172); // You must wait a few seconds before using another Healing Stone.
|
||||
return false;
|
||||
}
|
||||
|
||||
from.BeginAction<HealingStone>();
|
||||
Timer.StartTimer(TimeSpan.FromSeconds(UseCooldownSeconds), from.EndAction<HealingStone>);
|
||||
|
||||
if (from.Poisoned)
|
||||
{
|
||||
TryCure(from);
|
||||
return true;
|
||||
}
|
||||
|
||||
Replenish();
|
||||
|
||||
var toHeal = Math.Min(_healingAvailable, Math.Min(_storedLifeForce, from.HitsMax - from.Hits));
|
||||
if (toHeal <= 0)
|
||||
{
|
||||
from.SendLocalizedMessage(1115264); // Your healing stone does not have enough energy to remove the poison.
|
||||
return true;
|
||||
}
|
||||
|
||||
var oldHits = from.Hits;
|
||||
SpellHelper.Heal(toHeal, from, _ownerMobile);
|
||||
var healed = Math.Max(0, from.Hits - oldHits);
|
||||
|
||||
_storedLifeForce -= healed;
|
||||
_healingAvailable = Math.Min(1, _maximumHealing);
|
||||
_rechargeStarted = Core.Now;
|
||||
InvalidateProperties();
|
||||
|
||||
from.FixedParticles(0x376A, 9, 32, 5030, EffectLayer.Waist);
|
||||
from.PlaySound(0x202);
|
||||
|
||||
if (_storedLifeForce <= 0)
|
||||
{
|
||||
from.SendLocalizedMessage(1115266); // The healing stone has used up all its energy and has been destroyed.
|
||||
Delete();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void TryCure(Mobile from)
|
||||
{
|
||||
var poisonLevel = Poison.IncreaseLevel(from.Poison).Level;
|
||||
var cost = GetCureCost(poisonLevel);
|
||||
|
||||
if (_maximumLifeForce < cost)
|
||||
{
|
||||
from.SendLocalizedMessage(1115265); // Your Mysticism, Focus, or Imbuing Skills are not enough to use the heal stone to cure yourself.
|
||||
return;
|
||||
}
|
||||
|
||||
if (_storedLifeForce < cost)
|
||||
{
|
||||
from.SendLocalizedMessage(1115264); // Your healing stone does not have enough energy to remove the poison.
|
||||
LifeForce -= cost / 3;
|
||||
return;
|
||||
}
|
||||
|
||||
var chanceToCure = GetCureChance(
|
||||
MysticSpell.GetBaseSkill(from),
|
||||
Math.Max(from.Skills.Focus.Value, from.Skills.Imbuing.Value),
|
||||
poisonLevel
|
||||
);
|
||||
|
||||
if (chanceToCure > Utility.Random(100) && from.CurePoison(_ownerMobile))
|
||||
{
|
||||
from.SendLocalizedMessage(500231); // You feel cured of poison!
|
||||
from.FixedEffect(0x373A, 10, 15);
|
||||
from.PlaySound(0x1E0);
|
||||
LifeForce -= cost;
|
||||
}
|
||||
else
|
||||
{
|
||||
from.SendMessage("The Healing Stone failed to cure your poison.");
|
||||
LifeForce -= cost / 3;
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetHealingAfterPotion()
|
||||
{
|
||||
_healingAvailable = Math.Min(1, _maximumHealing);
|
||||
_rechargeStarted = Core.Now;
|
||||
InvalidateProperties();
|
||||
}
|
||||
|
||||
private void Replenish()
|
||||
{
|
||||
if (_healingAvailable >= _maximumHealing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var elapsed = Core.Now - _rechargeStarted;
|
||||
if (elapsed <= TimeSpan.Zero)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var recovered = (int)(elapsed.TotalSeconds * _maximumHealing / FullRechargeSeconds);
|
||||
if (recovered <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_healingAvailable = Math.Min(_maximumHealing, _healingAvailable + recovered);
|
||||
_rechargeStarted = _healingAvailable >= _maximumHealing
|
||||
? Core.Now
|
||||
: _rechargeStarted.AddSeconds(recovered * (double)FullRechargeSeconds / _maximumHealing);
|
||||
InvalidateProperties();
|
||||
}
|
||||
}
|
||||
|
|
@ -19,7 +19,9 @@ public abstract partial class BaseHealPotion : BasePotion
|
|||
var min = Scale(from, MinHeal);
|
||||
var max = Scale(from, MaxHeal);
|
||||
|
||||
from.Heal(Utility.RandomMinMax(min, max));
|
||||
var healed = Utility.RandomMinMax(min, max);
|
||||
from.Heal(healed);
|
||||
HealingStone.OnPotionHealed(from);
|
||||
}
|
||||
|
||||
public override bool CanDrink(Mobile from)
|
||||
|
|
|
|||
51
Projects/UOContent/Migrations/Server.Items.HealingStone.v0.json
generated
Normal file
51
Projects/UOContent/Migrations/Server.Items.HealingStone.v0.json
generated
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"version": 0,
|
||||
"type": "Server.Items.HealingStone",
|
||||
"properties": [
|
||||
{
|
||||
"name": "OwnerMobile",
|
||||
"type": "Server.Mobile",
|
||||
"rule": "SerializableInterfaceMigrationRule"
|
||||
},
|
||||
{
|
||||
"name": "StoredLifeForce",
|
||||
"type": "int",
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "MaximumLifeForce",
|
||||
"type": "int",
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "HealingAvailable",
|
||||
"type": "int",
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "MaximumHealing",
|
||||
"type": "int",
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "RechargeStarted",
|
||||
"type": "System.DateTime",
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -182,7 +182,7 @@ namespace Server.Spells
|
|||
{
|
||||
// Mysticism spells
|
||||
Register(677, typeof(NetherBoltSpell));
|
||||
// Register(678, typeof(HealingStoneSpell));
|
||||
Register(678, typeof(HealingStoneSpell));
|
||||
Register(679, typeof(PurgeMagicSpell));
|
||||
// Register(680, typeof(EnchantSpell));
|
||||
// Register(681, typeof(SleepSpell));
|
||||
|
|
|
|||
62
Projects/UOContent/Spells/Mysticism/HealingStoneSpell.cs
Normal file
62
Projects/UOContent/Spells/Mysticism/HealingStoneSpell.cs
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
using System;
|
||||
using Server.Items;
|
||||
|
||||
namespace Server.Spells.Mysticism;
|
||||
|
||||
public class HealingStoneSpell : MysticSpell
|
||||
{
|
||||
private static readonly SpellInfo _info = new(
|
||||
"Healing Stone",
|
||||
"Kal In Mani",
|
||||
-1,
|
||||
9002,
|
||||
Reagent.Bone,
|
||||
Reagent.Garlic,
|
||||
Reagent.Ginseng,
|
||||
Reagent.SpidersSilk
|
||||
);
|
||||
|
||||
public HealingStoneSpell(Mobile caster, Item scroll = null) : base(caster, scroll, _info)
|
||||
{
|
||||
}
|
||||
|
||||
public override SpellCircle Circle => SpellCircle.First;
|
||||
|
||||
public override TimeSpan CastDelayBase => TimeSpan.FromSeconds(5.0);
|
||||
|
||||
public override bool CheckCast()
|
||||
{
|
||||
if (!base.CheckCast())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Caster.Backpack == null)
|
||||
{
|
||||
Caster.SendLocalizedMessage(1042001); // That must be in your pack for you to use it.
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void OnCast()
|
||||
{
|
||||
if (CheckSequence())
|
||||
{
|
||||
Caster.Backpack.FindItemByType<HealingStone>()?.Delete();
|
||||
|
||||
var totalSkill = GetBaseSkill(Caster) + GetDamageSkill(Caster);
|
||||
var lifeForce = Math.Max(1, (int)(totalSkill * 1.25));
|
||||
var maxHealing = Math.Max(1, (int)(totalSkill / 6.0));
|
||||
var stone = new HealingStone(Caster, lifeForce, maxHealing);
|
||||
|
||||
Caster.AddToBackpack(stone);
|
||||
Caster.SendLocalizedMessage(1080115); // A Healing Stone appears in your backpack.
|
||||
Caster.FixedParticles(0x3779, 1, 30, 0x26B8, 0, 0, EffectLayer.Waist);
|
||||
Caster.PlaySound(0x650);
|
||||
}
|
||||
|
||||
FinishSequence();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue