using System.Collections.Generic; using ModernUO.Serialization; using Server.Collections; using Server.ContextMenus; using Server.Gumps; using Server.Items; using Server.Multis; using Server.Network; namespace Server.Engines.Plants; public enum PlantStatus { BowlOfDirt = 0, Seed = 1, Sapling = 2, Plant = 4, FullGrownPlant = 7, DecorativePlant = 10, DeadTwigs = 11, Stage1 = 1, Stage2 = 2, Stage3 = 3, Stage4 = 4, Stage5 = 5, Stage6 = 6, Stage7 = 7, Stage8 = 8, Stage9 = 9 } [SerializationGenerator(3, false)] public partial class PlantItem : Item, ISecurable { public static List Plants { get; } = []; [SerializedIgnoreDupe] [SerializableField(0)] [SaveFlag(nameof(ShouldSerializeSecureLevel))] [SerializedCommandProperty(AccessLevel.GameMaster)] private SecureLevel _level; private bool ShouldSerializeSecureLevel() => (int)_level != 0; [SerializedIgnoreDupe] [SerializableField(5, setter: "private")] [SaveFlag(nameof(ShouldSerializePlantSystem))] private PlantSystem _plantSystem; private bool ShouldSerializePlantSystem() => _plantStatus < PlantStatus.DecorativePlant; // For clients older than 7.0.12.0 private ObjectPropertyList _oldClientPropertyList; [Constructible] public PlantItem(bool fertileDirt = false) : base(0x1602) { _plantStatus = PlantStatus.BowlOfDirt; _plantSystem = new PlantSystem(this) { FertileDirt = fertileDirt }; _level = SecureLevel.Owner; Plants.Add(this); } public override double DefaultWeight => 1.0; public ObjectPropertyList OldClientPropertyList { get { InitializePropertyList(_oldClientPropertyList ??= new ObjectPropertyList(this)); return _oldClientPropertyList; } } public override bool ForceShowProperties => ObjectPropertyList.Enabled; [CommandProperty(AccessLevel.GameMaster)] [SerializableProperty(1)] [SaveFlag(nameof(ShouldSerializePlantStatus))] public PlantStatus PlantStatus { get => _plantStatus; set { if (_plantStatus == value || value is < PlantStatus.BowlOfDirt or > PlantStatus.DeadTwigs) { return; } var ratio = PlantSystem != null ? (double)PlantSystem.Hits / PlantSystem.MaxHits : 1.0; _plantStatus = value; if (_plantStatus >= PlantStatus.DecorativePlant) { PlantSystem = null; } else { PlantSystem ??= new PlantSystem(this); var hits = (int)(PlantSystem.MaxHits * ratio); if (hits == 0 && _plantStatus > PlantStatus.BowlOfDirt) { PlantSystem.Hits = 1; } else { PlantSystem.Hits = hits; } } Update(); } } private bool ShouldSerializePlantStatus() => _plantStatus != PlantStatus.BowlOfDirt; [SerializableProperty(2)] [SaveFlag(nameof(ShouldSerializePlantType))] [CommandProperty(AccessLevel.GameMaster)] public PlantType PlantType { get => _plantType; set { _plantType = value; Update(); } } private bool ShouldSerializePlantType() => (int)_plantType != 0; [SerializableProperty(3)] [SaveFlag(nameof(ShouldSerializePlantHue))] [CommandProperty(AccessLevel.GameMaster)] public PlantHue PlantHue { get => _plantHue; set { _plantHue = value; Update(); } } private bool ShouldSerializePlantHue() => _plantHue != PlantHue.None; [SerializableProperty(4)] [SaveFlag(nameof(ShouldSerializeShowType))] [CommandProperty(AccessLevel.GameMaster)] public bool ShowType { get => _showType; set { _showType = value; InvalidateProperties(); this.MarkDirty(); } } private bool ShouldSerializeShowType() => _showType; [CommandProperty(AccessLevel.GameMaster)] public bool ValidGrowthLocation { get { if (IsLockedDown && RootParent == null) { return true; } if (RootParent is not Mobile owner) { return false; } return IsChildOf(owner.Backpack) || IsChildOf(owner.FindBankNoCreate()); } } [CommandProperty(AccessLevel.GameMaster)] public bool IsGrowable => _plantStatus >= PlantStatus.BowlOfDirt && _plantStatus <= PlantStatus.Stage9; [CommandProperty(AccessLevel.GameMaster)] public bool IsCrossable => PlantHueInfo.IsCrossable(PlantHue) && PlantTypeInfo.IsCrossable(PlantType); [CommandProperty(AccessLevel.GameMaster)] public bool Reproduces => PlantHueInfo.CanReproduce(PlantHue) && PlantTypeInfo.CanReproduce(PlantType); public override void OnAfterDuped(Item newItem) { PlantSystem.OnAfterDuped(newItem); } public override void OnSingleClick(Mobile from) { if (_plantStatus >= PlantStatus.DeadTwigs) { LabelTo(from, LabelNumber); } else if (_plantStatus >= PlantStatus.DecorativePlant) { LabelTo(from, 1061924); // a decorative plant } else if (_plantStatus >= PlantStatus.FullGrownPlant) { LabelTo(from, PlantTypeInfo.GetInfo(_plantType).Name); } else { LabelTo(from, 1029913); // plant bowl } } public override void GetContextMenuEntries(Mobile from, ref PooledRefList list) { base.GetContextMenuEntries(from, ref list); SetSecureLevelEntry.AddTo(from, this, ref list); } public int GetLocalizedPlantStatus() { return _plantStatus switch { >= PlantStatus.Plant => 1060812, // plant >= PlantStatus.Sapling => 1023305, // sapling >= PlantStatus.Seed => 1060810, // seed _ => 1026951 // dirt }; } public static int GetLocalizedContainerType() => 1150435; private void Update() { if (_plantStatus >= PlantStatus.DeadTwigs) { ItemID = 0x1B9D; Hue = PlantHueInfo.GetInfo(_plantHue).Hue; } else if (_plantStatus >= PlantStatus.FullGrownPlant) { ItemID = PlantTypeInfo.GetInfo(_plantType).ItemID; Hue = PlantHueInfo.GetInfo(_plantHue).Hue; } else if (_plantStatus >= PlantStatus.Plant) { ItemID = 0x1600; Hue = 0; } else { ItemID = 0x1602; Hue = 0; } InvalidateProperties(); this.MarkDirty(); } private void InitializePropertyList(ObjectPropertyList list) { GetProperties(list); AppendChildProperties(list); list.Terminate(); } // Overridden to support new and old client localization public override void SendOPLPacketTo(NetState ns) { if (!ObjectPropertyList.Enabled) { return; } if (ns.Version < ClientVersion.Version70120) { ns.SendOPLInfo(Serial, OldClientPropertyList.Hash); return; } ns.SendOPLInfo(this); } public override void SendPropertiesTo(NetState ns) { if (ns?.Version < ClientVersion.Version70120) { ns?.Send(OldClientPropertyList.Buffer); return; } ns?.Send(PropertyList.Buffer); } public override void ClearProperties() { base.ClearProperties(); _oldClientPropertyList = null; } public override void InvalidateProperties() { base.InvalidateProperties(); if (!ObjectPropertyList.Enabled) { return; } if (Map != null && Map != Map.Internal && !World.Loading) { int? oldHash; int newHash; if (_oldClientPropertyList != null) { oldHash = _oldClientPropertyList.Hash; _oldClientPropertyList.Reset(); InitializePropertyList(_oldClientPropertyList); newHash = _oldClientPropertyList.Hash; } else { oldHash = null; newHash = OldClientPropertyList.Hash; } if (oldHash != newHash) { Delta(ItemDelta.Properties); } } else { ClearProperties(); } } public override void OnAosSingleClick(Mobile from) { var ns = from?.NetState; if (ns == null) { return; } var opl = ns.Version < ClientVersion.Version70120 ? OldClientPropertyList : PropertyList; if (opl.Header > 0) { from.NetState.SendMessageLocalized( Serial, ItemID, MessageType.Label, 0x3B2, 3, opl.Header, Name, opl.HeaderArgs ); } } public override void GetProperties(IPropertyList list) { if (_plantStatus >= PlantStatus.DeadTwigs) { base.GetProperties(list); return; } var typeInfo = PlantTypeInfo.GetInfo(_plantType); var hueInfo = PlantHueInfo.GetInfo(_plantHue); if (_plantStatus >= PlantStatus.DecorativePlant) { list.Add(typeInfo.GetPlantLabelDecorative(hueInfo), $"{hueInfo.Name:#}\t{typeInfo.Name:#}"); return; } var container = GetLocalizedContainerType(); var dirt = PlantSystem.GetLocalizedDirtStatus(); var health = PlantSystem.GetLocalizedHealth(); var plantStatus = GetLocalizedPlantStatus(); if (_plantStatus < PlantStatus.Seed) { // Clients above 7.0.12.0 use the regular PropertyList if (list != _oldClientPropertyList) { // a ~1_val~ of ~2_val~ dirt list.Add(1060830, $"{container:#}\t{dirt:#}"); } else { // a ~1_val~ of ~2_val~ dirt list.Add(1060830, $"{dirt:#}"); } return; } if (_plantStatus >= PlantStatus.FullGrownPlant) { list.Add( typeInfo.GetPlantLabelFullGrown(hueInfo), $"{health:#}\t{hueInfo.Name:#}\t{typeInfo.Name:#}" ); return; } if (_showType) { var plantNumber = _plantStatus == PlantStatus.Plant ? typeInfo.GetPlantLabelPlant(hueInfo) : typeInfo.GetPlantLabelSeed(hueInfo); if (list != _oldClientPropertyList) { list.Add( plantNumber, $"{container:#}\t{dirt:#}\t{health:#}\t{hueInfo.Name:#}\t{typeInfo.Name:#}\t{plantStatus:#}" ); } else { list.Add( plantNumber, $"{dirt:#}\t{health:#}\t{hueInfo.Name:#}\t{typeInfo.Name:#}\t{plantStatus:#}" ); } } else { var category = typeInfo.PlantCategory == PlantCategory.Default ? hueInfo.Name : (int)typeInfo.PlantCategory; var plantNumber = hueInfo.IsBright() ? 1060832 : 1060831; if (list != _oldClientPropertyList) { list.Add(plantNumber, $"{container:#}\t{dirt:#}\t{health:#}\t{category:#}\t{plantStatus:#}"); } else { list.Add(plantNumber, $"{dirt:#}\t{health:#}\t{category:#}\t{plantStatus:#}"); } } } public bool IsUsableBy(Mobile from) => IsChildOf(from.Backpack) || IsChildOf(from.FindBankNoCreate()) || IsLockedDown && IsAccessibleTo(from) || RootParent is Item root && root.IsSecure && root.IsAccessibleTo(from); public override void OnDoubleClick(Mobile from) { if (_plantStatus >= PlantStatus.DecorativePlant) { return; } if (!IsChildOf(from)) { var loc = GetWorldLocation(); if (!from.InLOS(loc) || !from.InRange(loc, 2)) { from.LocalOverheadMessage(MessageType.Regular, 0x3E9, 1019045); // I can't reach that return; } } if (!IsUsableBy(from)) { LabelTo(from, 1061856); // You must have the item in your backpack or locked down in order to use it. return; } MainPlantGump.DisplayTo(from, this); } public void PlantSeed(Mobile from, Seed seed) { if (_plantStatus >= PlantStatus.FullGrownPlant) { LabelTo(from, 1061919); // You must use a seed on some prepared soil! } else if (!IsUsableBy(from)) { LabelTo(from, 1061921); // The bowl of dirt must be in your pack, or you must lock it down. } else if (_plantStatus != PlantStatus.BowlOfDirt) { // This bowl of dirt already has a ~1_val~ in it! from.SendLocalizedMessage(1080389, $"#{GetLocalizedPlantStatus()}"); } else if (PlantSystem.Water < 2) { LabelTo(from, 1061920); // The dirt needs to be softened first. } else { PlantType = seed.PlantType; PlantHue = seed.PlantHue; ShowType = seed.ShowType; seed.Consume(); PlantStatus = PlantStatus.Seed; PlantSystem.Reset(false); LabelTo(from, 1061922); // You plant the seed in the bowl of dirt. } } public void Die() { if (_plantStatus >= PlantStatus.FullGrownPlant) { PlantStatus = PlantStatus.DeadTwigs; } else { PlantStatus = PlantStatus.BowlOfDirt; PlantSystem.Reset(true); } } public void Pour(Mobile from, Item item) { if (_plantStatus >= PlantStatus.DeadTwigs) { return; } if (_plantStatus == PlantStatus.DecorativePlant) { LabelTo(from, 1053049); // This is a decorative plant, it does not need watering! return; } if (!IsUsableBy(from)) { LabelTo(from, 1061856); // You must have the item in your backpack or locked down in order to use it. return; } if (item is BaseBeverage beverage) { if (beverage.IsEmpty || !beverage.Pourable || beverage.Content != BeverageType.Water) { LabelTo(from, 1053069); // You can't use that on a plant! return; } if (!beverage.ValidateUse(from, true)) { return; } beverage.Quantity--; PlantSystem.Water++; from.PlaySound(0x4E); LabelTo(from, 1061858); // You soften the dirt with water. } else if (item is BasePotion potion) { if (ApplyPotion(potion.PotionEffect, false, out var message)) { potion.Consume(); from.PlaySound(0x240); from.AddToBackpack(new Bottle()); } LabelTo(from, message); } else if (item is PotionKeg keg) { if (keg.Held <= 0) { LabelTo(from, 1053069); // You can't use that on a plant! return; } if (ApplyPotion(keg.Type, false, out var message)) { keg.Held--; from.PlaySound(0x240); } LabelTo(from, message); } else { LabelTo(from, 1053069); // You can't use that on a plant! } } public bool ApplyPotion(PotionEffect effect, bool testOnly, out int message) { if (_plantStatus >= PlantStatus.DecorativePlant) { message = 1053049; // This is a decorative plant, it does not need watering! return false; } if (_plantStatus == PlantStatus.BowlOfDirt) { message = 1053066; // You should only pour potions on a plant or seed! return false; } var full = false; if (effect is PotionEffect.PoisonGreater or PotionEffect.PoisonDeadly) { if (PlantSystem.IsFullPoisonPotion) { full = true; } else if (!testOnly) { PlantSystem.PoisonPotion++; } } else if (effect == PotionEffect.CureGreater) { if (PlantSystem.IsFullCurePotion) { full = true; } else if (!testOnly) { PlantSystem.CurePotion++; } } else if (effect == PotionEffect.HealGreater) { if (PlantSystem.IsFullHealPotion) { full = true; } else if (!testOnly) { PlantSystem.HealPotion++; } } else if (effect == PotionEffect.StrengthGreater) { if (PlantSystem.IsFullStrengthPotion) { full = true; } else if (!testOnly) { PlantSystem.StrengthPotion++; } } else if (effect is PotionEffect.PoisonLesser or PotionEffect.Poison or PotionEffect.CureLesser or PotionEffect.Cure or PotionEffect.HealLesser or PotionEffect.Heal or PotionEffect.Strength) { message = 1053068; // This potion is not powerful enough to use on a plant! return false; } else { message = 1053069; // You can't use that on a plant! return false; } if (full) { message = 1053065; // The plant is already soaked with this type of potion! return false; } message = 1053067; // You pour the potion over the plant. return true; } private void Deserialize(IGenericReader reader, int version) { _level = (SecureLevel)reader.ReadInt(); _plantStatus = (PlantStatus)reader.ReadInt(); _plantType = (PlantType)reader.ReadInt(); _plantHue = (PlantHue)reader.ReadInt(); _showType = reader.ReadBool(); if (_plantStatus < PlantStatus.DecorativePlant) { _plantSystem = new PlantSystem(this); _plantSystem.Deserialize(reader); } } [AfterDeserialization] private void AfterDeserialization() { Plants.Add(this); } public override void OnAfterDelete() { base.OnAfterDelete(); Plants.Remove(this); } }