diff --git a/Intersect.Server.Core/Database/Item.cs b/Intersect.Server.Core/Database/Item.cs index 6dbaf60b91..f45309155e 100644 --- a/Intersect.Server.Core/Database/Item.cs +++ b/Intersect.Server.Core/Database/Item.cs @@ -5,13 +5,14 @@ using Intersect.GameObjects; using Intersect.Network.Packets.Server; using Intersect.Server.Database.PlayerData.Players; +using Intersect.Server.Framework.Items; using Newtonsoft.Json; namespace Intersect.Server.Database; -public class Item +public class Item: IItem { - [JsonIgnore][NotMapped] public double DropChance = 100; + [JsonIgnore][NotMapped] public double DropChance { get; set; } = 100; public Item() { diff --git a/Intersect.Server.Core/Entities/Entity.cs b/Intersect.Server.Core/Entities/Entity.cs index a8a480b120..5c08fb5bbb 100644 --- a/Intersect.Server.Core/Entities/Entity.cs +++ b/Intersect.Server.Core/Entities/Entity.cs @@ -10,6 +10,8 @@ using Intersect.Server.Database.PlayerData.Players; using Intersect.Server.Entities.Combat; using Intersect.Server.Entities.Events; +using Intersect.Server.Framework.Entities; +using Intersect.Server.Framework.Items; using Intersect.Server.General; using Intersect.Server.Localization; using Intersect.Server.Maps; @@ -21,12 +23,13 @@ namespace Intersect.Server.Entities; -public abstract partial class Entity : IDisposable +public abstract partial class Entity : IEntity { //Instance Values private Guid _id = Guid.NewGuid(); - - public Guid MapInstanceId = Guid.Empty; + + [NotMapped] + public Guid MapInstanceId { get; set; } = Guid.Empty; [JsonProperty("MaxVitals"), NotMapped] private long[] _maxVital = new long[Enum.GetValues().Length]; @@ -3056,7 +3059,8 @@ protected virtual void DropItems(Entity killer, bool sendUpdate = true) // Spawn the actual item! if (MapController.TryGetInstanceFromMap(MapId, MapInstanceId, out var instance)) { - instance.SpawnItem(X, Y, drop, drop.Quantity, lootOwner, sendUpdate); + var itemSource = this.AsItemSource(); + instance.SpawnItem(itemSource, X, Y, drop, drop.Quantity, lootOwner, sendUpdate); } // Process the drop (for players this would remove it from their inventory) @@ -3064,6 +3068,8 @@ protected virtual void DropItems(Entity killer, bool sendUpdate = true) } } + protected abstract EntityItemSource? AsItemSource(); + public bool IsDead() { return Dead; diff --git a/Intersect.Server.Core/Entities/Events/EventPageInstance.cs b/Intersect.Server.Core/Entities/Events/EventPageInstance.cs index 9ac20b12db..163e27804b 100644 --- a/Intersect.Server.Core/Entities/Events/EventPageInstance.cs +++ b/Intersect.Server.Core/Entities/Events/EventPageInstance.cs @@ -3,6 +3,8 @@ using Intersect.GameObjects.Events; using Intersect.Network.Packets.Server; using Intersect.Server.Entities.Pathfinding; +using Intersect.Server.Framework; +using Intersect.Server.Framework.Items; using Intersect.Server.Maps; using Intersect.Server.Networking; using Intersect.Utilities; @@ -892,5 +894,10 @@ public bool ShouldDespawn(MapController map) return false; } + + protected override EntityItemSource? AsItemSource() + { + return null; + } } diff --git a/Intersect.Server.Core/Entities/Npc.cs b/Intersect.Server.Core/Entities/Npc.cs index 61b27c3525..db0125106e 100644 --- a/Intersect.Server.Core/Entities/Npc.cs +++ b/Intersect.Server.Core/Entities/Npc.cs @@ -8,6 +8,8 @@ using Intersect.Server.Entities.Combat; using Intersect.Server.Entities.Events; using Intersect.Server.Entities.Pathfinding; +using Intersect.Server.Framework.Entities; +using Intersect.Server.Framework.Items; using Intersect.Server.Maps; using Intersect.Server.Networking; using Intersect.Utilities; @@ -1671,5 +1673,15 @@ public override EntityPacket EntityPacket(EntityPacket packet = null, Player for return pkt; } + + protected override EntityItemSource? AsItemSource() + { + return new EntityItemSource + { + EntityType = GetEntityType(), + EntityReference = new WeakReference(this), + Id = this.Base.Id + }; + } } diff --git a/Intersect.Server.Core/Entities/Player.cs b/Intersect.Server.Core/Entities/Player.cs index b23d674e02..1c1a83ba30 100644 --- a/Intersect.Server.Core/Entities/Player.cs +++ b/Intersect.Server.Core/Entities/Player.cs @@ -19,6 +19,8 @@ using Intersect.Server.Database.PlayerData.Security; using Intersect.Server.Entities.Combat; using Intersect.Server.Entities.Events; +using Intersect.Server.Framework.Entities; +using Intersect.Server.Framework.Items; using Intersect.Server.Localization; using Intersect.Server.Maps; using Intersect.Server.Networking; @@ -35,7 +37,6 @@ public partial class Player : Entity { [NotMapped, JsonIgnore] public Guid PreviousMapInstanceId = Guid.Empty; - //Online Players List private static readonly ConcurrentDictionary OnlinePlayers = new ConcurrentDictionary(); @@ -2781,7 +2782,7 @@ public bool TryGiveItem(Item item, ItemHandling handler = ItemHandling.Normal, b // Do we have any items to spawn to the map? if (spawnAmount > 0 && MapController.TryGetInstanceFromMap(Map.Id, MapInstanceId, out var instance)) { - instance.SpawnItem(overflowTileX > -1 ? overflowTileX : X, overflowTileY > -1 ? overflowTileY : Y, item, spawnAmount, Id); + instance.SpawnItem(AsItemSource(), overflowTileX > -1 ? overflowTileX : X, overflowTileY > -1 ? overflowTileY : Y, item, spawnAmount, Id); return spawnAmount != item.Quantity; } @@ -2820,8 +2821,21 @@ public bool TryGiveItem(Item item, ItemHandling handler = ItemHandling.Normal, b var bankInterface = new BankInterface(this, ((IEnumerable)Bank).ToList(), new object(), null, Options.Instance.Bank.MaxSlots); return bankOverflow && bankInterface.TryDepositItem(item, sendUpdate); } - - + + /// + /// Creates an item source for the player entity. + /// + /// A new object. + protected override EntityItemSource? AsItemSource() + { + return new EntityItemSource + { + EntityType = GetEntityType(), + EntityReference = new WeakReference(this), + Id = this.Id + }; + } + /// /// Gives the player an item. NOTE: This method MAKES ZERO CHECKS to see if this is possible! /// Use TryGiveItem where possible! @@ -3135,7 +3149,7 @@ public bool TryDropItemFrom(int slotIndex, int amount) return false; } - mapInstance.SpawnItem(X, Y, itemInSlot, itemDescriptor.IsStackable ? amount : 1, Id); + mapInstance.SpawnItem(AsItemSource(),X, Y, itemInSlot, itemDescriptor.IsStackable ? amount : 1, Id); itemInSlot.Quantity = Math.Max(0, itemInSlot.Quantity - amount); @@ -4973,6 +4987,8 @@ public void ReturnTradeItems() return; } + var itemSource = AsItemSource(); + foreach (var offer in Trading.Offer) { if (offer == null || offer.ItemId == Guid.Empty) @@ -4982,7 +4998,7 @@ public void ReturnTradeItems() if (!TryGiveItem(offer, -1) && MapController.TryGetInstanceFromMap(MapId, MapInstanceId, out var instance)) { - instance.SpawnItem(X, Y, offer, offer.Quantity, Id); + instance.SpawnItem(itemSource, X, Y, offer, offer.Quantity, Id); PacketSender.SendChatMsg(this, Strings.Trading.ItemsDropped, ChatMessageType.Inventory, CustomColors.Alerts.Error); } diff --git a/Intersect.Server.Core/Entities/Projectile.cs b/Intersect.Server.Core/Entities/Projectile.cs index c7be99c6ee..f132707070 100644 --- a/Intersect.Server.Core/Entities/Projectile.cs +++ b/Intersect.Server.Core/Entities/Projectile.cs @@ -4,6 +4,7 @@ using Intersect.Network.Packets.Server; using Intersect.Server.Database; using Intersect.Server.Entities.Combat; +using Intersect.Server.Framework.Items; using Intersect.Server.Maps; using Intersect.Utilities; using MapAttribute = Intersect.Enums.MapAttribute; @@ -654,5 +655,10 @@ public override EntityType GetEntityType() { return EntityType.Projectile; } + + protected override EntityItemSource? AsItemSource() + { + return null; + } } diff --git a/Intersect.Server.Core/Entities/Resource.cs b/Intersect.Server.Core/Entities/Resource.cs index 12fc1bdf26..5d42f7ab01 100644 --- a/Intersect.Server.Core/Entities/Resource.cs +++ b/Intersect.Server.Core/Entities/Resource.cs @@ -3,6 +3,8 @@ using Intersect.Network.Packets.Server; using Intersect.Server.Database; using Intersect.Server.Database.PlayerData.Players; +using Intersect.Server.Framework.Entities; +using Intersect.Server.Framework.Items; using Intersect.Server.Maps; using Intersect.Server.Networking; using Intersect.Utilities; @@ -156,6 +158,8 @@ public void SpawnResourceItems(Entity killer) { selectedTile = tiles[Randomization.Next(0, tiles.Count)]; } + + var itemSource = AsItemSource(); // Drop items foreach (var item in Items) @@ -165,7 +169,7 @@ public void SpawnResourceItems(Entity killer) var mapId = selectedTile.GetMapId(); if (MapController.TryGetInstanceFromMap(mapId, MapInstanceId, out var mapInstance)) { - mapInstance.SpawnItem(selectedTile.GetX(), selectedTile.GetY(), item, item.Quantity, killer.Id); + mapInstance.SpawnItem(itemSource, selectedTile.GetX(), selectedTile.GetY(), item, item.Quantity, killer.Id); } } } @@ -173,6 +177,16 @@ public void SpawnResourceItems(Entity killer) Items.Clear(); } + + protected override EntityItemSource? AsItemSource() + { + return new EntityItemSource + { + EntityType = GetEntityType(), + EntityReference = new WeakReference(this), + Id = this.Base.Id + }; + } public override void ProcessRegen() { diff --git a/Intersect.Server.Core/Maps/MapInstance.cs b/Intersect.Server.Core/Maps/MapInstance.cs index 546bf18900..a00e5b05ca 100644 --- a/Intersect.Server.Core/Maps/MapInstance.cs +++ b/Intersect.Server.Core/Maps/MapInstance.cs @@ -13,6 +13,9 @@ using Intersect.Server.Classes.Maps; using MapAttribute = Intersect.Enums.MapAttribute; using Intersect.Server.Core.MapInstancing; +using Intersect.Server.Framework.Items; +using Intersect.Server.Framework.Maps; +using Intersect.Server.Plugins.Helpers; namespace Intersect.Server.Maps; @@ -47,7 +50,7 @@ namespace Intersect.Server.Maps; /// /// /// -public partial class MapInstance : IDisposable +public partial class MapInstance : IMapInstance { /// /// Reference to stay consistent/easy-to-read with overworld behavior @@ -78,7 +81,7 @@ public partial class MapInstance : IDisposable /// Note that this is NOT the Instance instance identifier - that is /// /// - public Guid Id; + public Guid Id { get; set; } /// /// An ID referring to which instance this processer belongs to. @@ -87,7 +90,7 @@ public partial class MapInstance : IDisposable /// will be processed and fed packets by that processer. /// /// - public Guid MapInstanceId; + public Guid MapInstanceId { get; set; } /// /// The last time the made a call to . @@ -141,7 +144,7 @@ public partial class MapInstance : IDisposable // Animations & Text private MapActionMessages mActionMessages = new MapActionMessages(); private MapAnimations mMapAnimations = new MapAnimations(); - + public MapInstance(MapController map, Guid mapInstanceId, Player creator) { mMapController = map; @@ -700,10 +703,9 @@ private void DespawnResources() /// /// Add a map item to this map. /// - /// The X location of this item. - /// The Y location of this item. + /// The source of the item, e.g. a player who dropped it, or a monster who spawned it on death, or the map instance in which it was spawned. /// The to add to the map. - private void AddItem(MapItem item) + private void AddItem(IItemSource? source, MapItem item) { AllMapItems.TryAdd(item.UniqueId, item); @@ -713,26 +715,30 @@ private void AddItem(MapItem item) } TileItems[item.TileIndex]?.TryAdd(item.UniqueId, item); + + MapHelper.Instance.InvokeItemAdded(source, item); } /// /// Spawn an item to this map instance. /// + /// The source of the item, e.g. a player who dropped it, or a monster who spawned it on death, or the map instance in which it was spawned /// The horizontal location of this item /// The vertical location of this item. /// The to spawn on the map. /// The amount of times to spawn this item to the map. Set to the quantity, overwrites quantity if stackable! - public void SpawnItem(int x, int y, Item item, int amount) => SpawnItem(x, y, item, amount, Guid.Empty); + public void SpawnItem(IItemSource? source, int x, int y, Item item, int amount) => SpawnItem(source, x, y, item, amount, Guid.Empty); /// /// Spawn an item to this map instance. /// + /// The source of the item, e.g. a player who dropped it, or a monster who spawned it on death, or the map instance in which it was spawned /// The horizontal location of this item /// The vertical location of this item. /// The to spawn on the map. /// The amount of times to spawn this item to the map. Set to the quantity, overwrites quantity if stackable! /// The player Id that will be the temporary owner of this item. - public void SpawnItem(int x, int y, Item item, int amount, Guid owner, bool sendUpdate = true) + public void SpawnItem(IItemSource? source, int x, int y, Item item, int amount, Guid owner, bool sendUpdate = true) { if (item == null) { @@ -792,7 +798,7 @@ public void SpawnItem(int x, int y, Item item, int amount, Guid owner, bool send } // Drop the new item. - AddItem(mapItem); + AddItem(source, mapItem); if (sendUpdate) { PacketSender.SendMapItemUpdate(mMapController.Id, MapInstanceId, mapItem, false); @@ -822,7 +828,7 @@ public void SpawnItem(int x, int y, Item item, int amount, Guid owner, bool send return; } - AddItem(mapItem); + AddItem(source, mapItem); } PacketSender.SendMapItemsToProximity(mMapController.Id, this); } @@ -924,7 +930,15 @@ private void SpawnAttributeItem(int x, int y) { mapItem.Quantity = 1; } - AddItem(mapItem); + + var mapItemSource = new MapItemSource + { + Id = MapInstanceId, + MapInstanceReference = new WeakReference(this), + DescriptorId = mMapController.Id, + }; + + AddItem(mapItemSource, mapItem); PacketSender.SendMapItemUpdate(mMapController.Id, MapInstanceId, mapItem, false); } } diff --git a/Intersect.Server.Core/Plugins/Helpers/MapHelper.cs b/Intersect.Server.Core/Plugins/Helpers/MapHelper.cs new file mode 100644 index 0000000000..5e3cb7fe23 --- /dev/null +++ b/Intersect.Server.Core/Plugins/Helpers/MapHelper.cs @@ -0,0 +1,30 @@ +using System.Diagnostics.CodeAnalysis; +using Intersect.Server.Framework.Items; +using Intersect.Server.Framework.Maps; +using Intersect.Server.Maps; + +namespace Intersect.Server.Plugins.Helpers; + +public partial class MapHelper : IMapHelper +{ + private static readonly Lazy _instance = new Lazy(() => new MapHelper()); + public static MapHelper Instance => _instance.Value; + private MapHelper() { } + public event ItemAddedHandler ItemAdded; + + public bool TryGetMapInstance(Guid mapId, Guid instanceId, [NotNullWhen(true)] out IMapInstance? instance) + { + instance = null; + if (MapController.TryGetInstanceFromMap(mapId, instanceId, out var mapInstance)) + { + instance = mapInstance; + return mapInstance != null; + } + return false; + } + + public void InvokeItemAdded(IItemSource source, IItem item) + { + ItemAdded?.Invoke(source, item); + } +} \ No newline at end of file diff --git a/Intersect.Server.Core/Plugins/ServerPluginContext.cs b/Intersect.Server.Core/Plugins/ServerPluginContext.cs index 0a497ab67b..b5a8f212d4 100644 --- a/Intersect.Server.Core/Plugins/ServerPluginContext.cs +++ b/Intersect.Server.Core/Plugins/ServerPluginContext.cs @@ -38,8 +38,11 @@ public IPluginContext Create(params object[] args) public ServerPluginContext(Plugin plugin) : base(plugin) { Lifecycle = new ServerLifecycleHelper(this); + MapHelper = Helpers.MapHelper.Instance; } /// public override IServerLifecycleHelper Lifecycle { get; } + + public IMapHelper MapHelper { get; } } diff --git a/Intersect.Server.Framework/Entities/IEntity.cs b/Intersect.Server.Framework/Entities/IEntity.cs new file mode 100644 index 0000000000..f468e2964a --- /dev/null +++ b/Intersect.Server.Framework/Entities/IEntity.cs @@ -0,0 +1,8 @@ +namespace Intersect.Server.Framework.Entities; + +public interface IEntity: IDisposable +{ + Guid Id { get; } + string Name { get; } + Guid MapInstanceId { get; } +} \ No newline at end of file diff --git a/Intersect.Server.Framework/Items/EntityItemSource.cs b/Intersect.Server.Framework/Items/EntityItemSource.cs new file mode 100644 index 0000000000..66bc82381e --- /dev/null +++ b/Intersect.Server.Framework/Items/EntityItemSource.cs @@ -0,0 +1,12 @@ +using Intersect.Enums; +using Intersect.Server.Framework.Entities; + +namespace Intersect.Server.Framework.Items; + +public partial class EntityItemSource: IItemSource +{ + public Guid Id { get; init; } + public ItemSourceType SourceType => ItemSourceType.Entity; + public EntityType EntityType { get; init; } + public WeakReference EntityReference { get; init; } +} \ No newline at end of file diff --git a/Intersect.Server.Framework/Items/EntityItemSource`1.cs b/Intersect.Server.Framework/Items/EntityItemSource`1.cs new file mode 100644 index 0000000000..9e5254500e --- /dev/null +++ b/Intersect.Server.Framework/Items/EntityItemSource`1.cs @@ -0,0 +1,21 @@ +using Intersect.Server.Framework.Entities; + +namespace Intersect.Server.Framework.Items; + +public class EntityItemSource : EntityItemSource where TEntity : class, IEntity +{ + private WeakReference _entityReference; + + public new WeakReference EntityReference + { + get => _entityReference; + init + { + _entityReference = value; + if (_entityReference.TryGetTarget(out TEntity target)) + { + base.EntityReference = new WeakReference(target); + } + } + } +} \ No newline at end of file diff --git a/Intersect.Server.Framework/Items/IItem.cs b/Intersect.Server.Framework/Items/IItem.cs new file mode 100644 index 0000000000..6ce2bdd441 --- /dev/null +++ b/Intersect.Server.Framework/Items/IItem.cs @@ -0,0 +1,12 @@ +using Intersect.GameObjects; + +namespace Intersect.Server.Framework.Items; + +public interface IItem +{ + Guid ItemId { get; } + ItemBase Descriptor { get; } + int Quantity { get; } + string ItemName { get; } + double DropChance { get; } +} \ No newline at end of file diff --git a/Intersect.Server.Framework/Items/IItemSource.cs b/Intersect.Server.Framework/Items/IItemSource.cs new file mode 100644 index 0000000000..e63415fa93 --- /dev/null +++ b/Intersect.Server.Framework/Items/IItemSource.cs @@ -0,0 +1,7 @@ +namespace Intersect.Server.Framework.Items; + +public interface IItemSource +{ + Guid Id { get; } + ItemSourceType SourceType { get; } +} \ No newline at end of file diff --git a/Intersect.Server.Framework/Items/ItemSourceType.cs b/Intersect.Server.Framework/Items/ItemSourceType.cs new file mode 100644 index 0000000000..caa78a4149 --- /dev/null +++ b/Intersect.Server.Framework/Items/ItemSourceType.cs @@ -0,0 +1,8 @@ +namespace Intersect.Server.Framework.Items; + +public enum ItemSourceType +{ + Unknown = 0, + Entity = 1, + Map = 2, +} \ No newline at end of file diff --git a/Intersect.Server.Framework/Items/MapItemSource.cs b/Intersect.Server.Framework/Items/MapItemSource.cs new file mode 100644 index 0000000000..041506e509 --- /dev/null +++ b/Intersect.Server.Framework/Items/MapItemSource.cs @@ -0,0 +1,11 @@ +using Intersect.Server.Framework.Maps; + +namespace Intersect.Server.Framework.Items; + +public partial class MapItemSource : IItemSource +{ + public Guid Id { get; init; } + public ItemSourceType SourceType => ItemSourceType.Map; + public WeakReference MapInstanceReference { get; init; } + public Guid DescriptorId { get; init; } +} \ No newline at end of file diff --git a/Intersect.Server.Framework/Maps/IMapInstance.cs b/Intersect.Server.Framework/Maps/IMapInstance.cs new file mode 100644 index 0000000000..e45493f40d --- /dev/null +++ b/Intersect.Server.Framework/Maps/IMapInstance.cs @@ -0,0 +1,7 @@ +namespace Intersect.Server.Framework.Maps; + +public interface IMapInstance: IDisposable +{ + Guid Id { get; } + public Guid MapInstanceId { get; } +} \ No newline at end of file diff --git a/Intersect.Server.Framework/Plugins/Helpers/IMapHelper.cs b/Intersect.Server.Framework/Plugins/Helpers/IMapHelper.cs new file mode 100644 index 0000000000..6522af5e20 --- /dev/null +++ b/Intersect.Server.Framework/Plugins/Helpers/IMapHelper.cs @@ -0,0 +1,13 @@ +using System.Diagnostics.CodeAnalysis; +using Intersect.Server.Framework.Items; +using Intersect.Server.Framework.Maps; + +namespace Intersect.Server.Plugins.Helpers; + +public interface IMapHelper +{ + event ItemAddedHandler ItemAdded; + bool TryGetMapInstance(Guid mapId, Guid instanceId, [NotNullWhen(true)] out IMapInstance? instance); +} + +public delegate void ItemAddedHandler(IItemSource? source, IItem item); diff --git a/Intersect.Server.Framework/Plugins/IServerPluginContext.cs b/Intersect.Server.Framework/Plugins/IServerPluginContext.cs index 8898062945..d7fe8a1761 100644 --- a/Intersect.Server.Framework/Plugins/IServerPluginContext.cs +++ b/Intersect.Server.Framework/Plugins/IServerPluginContext.cs @@ -8,6 +8,6 @@ namespace Intersect.Server.Plugins /// public interface IServerPluginContext : IPluginContext { - + IMapHelper MapHelper { get; } } }