using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Island.StandardLib.Storage { public class PlayerPackage : IStorable { public static RunDevice Device { get; private set; } static Dictionary ItemBehaviors; static AbstractController Controller; public static void DefineItemBehaviors(AbstractController controller, RunDevice device, params AbstractItemBehavior[] behaviors) { Device = device; Controller = controller; if (ItemBehaviors != null) throw new Exception("ItemBehavior is already defined."); ItemBehaviors = new Dictionary(); foreach (AbstractItemBehavior behavior in behaviors) { if (ItemBehaviors.ContainsKey(behavior.Id)) throw new Exception($"Redefined ItemBehavior, Id = {behavior.Id}"); ItemBehaviors.Add(behavior.Id, behavior); } } public static AbstractItemBehavior GetItemBehavior(int id) { if (ItemBehaviors == null) throw new Exception("ItemBehavior is undefined. Use DefineItemBehaviors(...) to set behaviors first."); if (ItemBehaviors.TryGetValue(id, out AbstractItemBehavior behavior)) return behavior; throw new Exception($"GetItemBehavior() Use undefined item behavior Id = {id}"); } public class ItemData : IStorable { public int DataType; public byte[] Data; public MultData DataVisitor { get => new MultData(Data); set => Data = value.Data; } public ItemData() { DataType = 0; Data = new byte[0]; } public ItemData(int dataType, MultData data) { DataType = dataType; Data = data.Data; } public ItemData(object data, int iStorageType = 10) { if (data is MultData) { DataVisitor = (MultData)data; DataType = 7; } else if (data is byte[]) { DataVisitor = new MultData((byte[])data); DataType = 7; } else if (data is int) { DataVisitor = new MultData((int)data); DataType = 1; } else if (data is float) { DataVisitor = new MultData((float)data); DataType = 2; } else if (data is string) { DataVisitor = new MultData((string)data); DataType = 0; } else if (data is long) { DataVisitor = new MultData((long)data); DataType = 3; } else if (data is uint) { DataVisitor = new MultData((uint)data); DataType = 5; } else if (data is ulong) { DataVisitor = new MultData((ulong)data); DataType = 6; } else if (data is double) { DataVisitor = new MultData((double)data); DataType = 4; } else if (data is IStorable) { DataVisitor = new MultData((IStorable)data); DataType = iStorageType; } else throw new InvalidCastException(); } public bool IsString => DataType == 0; public bool IsInt => DataType == 1; public bool IsFloat => DataType == 2; public bool IsLong => DataType == 3; public bool IsDouble => DataType == 4; public bool IsUInt => DataType == 5; public bool IsULong => DataType == 6; public bool IsByteArray => DataType == 7; public bool IsIStorable => DataType >= 10; public T As() where T : IStorable, new() { DataStorage ds = new DataStorage(Data); T t = new T(); t.ReadFromData(ds); return t; } public void SetData(T data) where T : IStorable, new() { DataStorage ds = new DataStorage(); data.WriteToData(ds); Data = ds.Bytes; } public void ReadFromData(DataStorage data) { data.ReadUncheck(out DataType); Data = data.Read(); } public void WriteToData(DataStorage data) { data.WriteUncheck(DataType); data.Write(Data); } } public class Item : IStorable { public Item() : this(0, 0) { } public Item(int typeId, int instanceId, int stackCount = 1, StorableFixedArray data = null) { Data = data ?? new StorableFixedArray(); TypeId = typeId; InstanceId = instanceId; StackCount = stackCount; } public int TypeId; public int InstanceId; public int StackCount; public StorableFixedArray Data; public ItemData this[int index] => Data[index]; public int Length => Data.Length; public void ConsumeOne(ConnectionPlayerBase who, PlayerPackage package) { StackCount--; ServerSetModify(who, package); } public void ServerSetModify(ConnectionPlayerBase who, PlayerPackage package) { if (Device == RunDevice.Client) throw new Exception("SetModify only can use in Server mode."); if (StackCount <= 0) { lock (package.objects) package.objects.Remove(InstanceId); Controller.OnServerSendDeleteCommand(who, InstanceId); } else Controller.OnServerSendModifyCommand(who, this); } internal void ClientApplyChange(Item item) { TypeId = item.TypeId; StackCount = item.StackCount; Data = item.Data; } public bool CanUse => GetItemBehavior(TypeId).CanUse(this); public void ServerUse(ConnectionPlayerBase who) { if (!CanUse) return; GetItemBehavior(TypeId).ServerUse(who, this); } public void ClientUse() { if (!CanUse) return; GetItemBehavior(TypeId).ClientUse(this); Controller.OnClientSendUseCommand(InstanceId); } public void WriteToData(DataStorage data) { data.WriteUncheck(TypeId); data.WriteUncheck(InstanceId); data.WriteUncheck(StackCount); data.Write(Data); } public void ReadFromData(DataStorage data) { data.ReadUncheck(out TypeId); data.ReadUncheck(out InstanceId); data.ReadUncheck(out StackCount); data.Read(out Data); } } public void OnServerReceiveUseCommand(ConnectionPlayerBase who, int instanceId) { Item item = FindItem(instanceId); if (item == null) Logger.Log(LogLevel.Error, $"Player {who.Nick} trying to use a nonexists item. InstanceId {instanceId}"); else item.ServerUse(who); } public abstract class AbstractItemBehavior { public abstract int Id { get; } public abstract void ServerUse(ConnectionPlayerBase who, Item item); public abstract void ClientUse(Item item); public abstract bool CanUse(Item item); public abstract int CanStack(Item root, Item append); } public abstract class AbstractUnstackableItemBehavior : AbstractItemBehavior { public override int CanStack(Item root, Item append) => 0; } public abstract class AbstractStackableItemBehavior : AbstractItemBehavior { public abstract int MaxStackSize { get; } protected virtual bool CompareStackCondition(Item root, Item append) { return root.Data.GetBytes().ByteEquals(append.Data.GetBytes()); } public override int CanStack(Item root, Item append) { if (!CompareStackCondition(root, append)) return 0; return MaxStackSize - root.StackCount; } } public abstract class AbstractController { public abstract void OnServerSendModifyCommand(ConnectionPlayerBase who, Item changedItem); public abstract void OnServerSendDeleteCommand(ConnectionPlayerBase who, int itemInstanceId); public abstract void OnServerSendChangeMaxItemSizeCommand(ConnectionPlayerBase who, int newMaxItemSizeValue); public abstract void OnClientSendUseCommand(int itemInstanceId); } public PlayerPackage() : this(30) { } public PlayerPackage(int maxItemSize) { if (ItemBehaviors == null) throw new Exception("ItemBehavior is undefined. Use DefineItemBehaviors(...) to set behaviors first."); objects = new StorableDictionary(); idAllocator = 0; this.maxItemSize = maxItemSize; } public StorableDictionary objects; int maxItemSize; int idAllocator; public void SetMaxItemSize(int newValue, ConnectionPlayerBase who = null) { if (Device == RunDevice.Server) { if (maxItemSize == newValue) return; if (maxItemSize > newValue) throw new ArgumentException("MaxItemSize: new value must be upper than current value."); maxItemSize = newValue; Controller.OnServerSendChangeMaxItemSizeCommand(who, newValue); } else { maxItemSize = newValue; } } public int MaxItemSize => maxItemSize; public int ItemSpace { get { lock (objects) return objects.Count; } } public int EmptySpace => maxItemSize - ItemSpace; public int CreateItem(ConnectionPlayerBase who, int typeId, int stackCount = 1, StorableFixedArray data = null) { if (Device == RunDevice.Client) throw new Exception("CreateItem only can use in Server mode."); Item thisObject = new Item(typeId, -1, stackCount, data == null ? null : data.MemoryCopy()); lock (objects) { foreach (var t in objects) { if (t.Value.TypeId == thisObject.TypeId) { int canStackCount = GetItemBehavior(t.Value.TypeId).CanStack(t.Value, thisObject); canStackCount = canStackCount <= thisObject.StackCount ? canStackCount : thisObject.StackCount; if (canStackCount > 0) { t.Value.StackCount += canStackCount; if (who != null) t.Value.ServerSetModify(who, this); thisObject.StackCount -= canStackCount; } if (thisObject.StackCount == 0) { thisObject = null; break; } } } if (thisObject != null) { int singleStk = GetItemBehavior(typeId).CanStack(new Item(typeId, 0, 0, data), new Item(typeId, 0, thisObject.StackCount, data)); do { if (objects.Count >= maxItemSize) return thisObject.StackCount; int curStack = thisObject.StackCount > singleStk ? singleStk : thisObject.StackCount; Item curItem = new Item(typeId, idAllocator++, curStack, data); objects.Add(curItem.InstanceId, curItem); if (who != null) curItem.ServerSetModify(who, this); thisObject.StackCount -= curStack; } while (thisObject.StackCount > 0); } } return 0; } public Item FindItem(int instanceId) { if (objects.TryGetValue(instanceId, out Item item)) return item; return null; } public int CreateItem(ConnectionPlayerBase who, Item copyItem) { return CreateItem(who, copyItem.TypeId, copyItem.StackCount, copyItem.Data); } public void DeleteItemWithInstance(ConnectionPlayerBase who, int itemInstanceId, int stackCount = 1) { if (Device == RunDevice.Client) throw new Exception("DeleteItem only can use in Server mode."); lock (objects) { if (objects.TryGetValue(itemInstanceId, out Item it)) { if (it.StackCount > stackCount) { it.StackCount -= stackCount; it.ServerSetModify(who, this); } else if (it.StackCount == stackCount) { objects.Remove(itemInstanceId); Controller.OnServerSendDeleteCommand(who, itemInstanceId); } else throw new Exception($"Trying delete InstanceId = {itemInstanceId} with StackCount = {stackCount}, no such stack count."); } else throw new Exception($"The InstanceId = {itemInstanceId} is not founded."); } } public void DeleteItemWithType(ConnectionPlayerBase who, int typeId, int stackCount = 1, Func checker = null) { if (Device == RunDevice.Client) throw new Exception("DeleteItem only can use in Server mode."); lock (objects) { List preDeletes = new List(); foreach (var obj in objects) { if (obj.Value.TypeId == typeId) { if (checker != null) { if (!checker(obj.Value)) continue; } if (obj.Value.StackCount > stackCount) { obj.Value.StackCount -= stackCount; stackCount = 0; if (who != null) obj.Value.ServerSetModify(who, this); break; } else { stackCount -= obj.Value.StackCount; preDeletes.Add(obj.Key); if (who != null) Controller.OnServerSendDeleteCommand(who, obj.Key); if (stackCount == 0) break; } } } if (stackCount != 0) throw new Exception("DeleteItem: FATAL ERROR: Count is not enough, but still maybe reduce some stack count, that already caught item lost."); for (int i = 0; i < preDeletes.Count; i++) objects.Remove(preDeletes[i]); } } public bool CheckItems(int typeId, int stackCount = 1, Func checker = null) { lock (objects) { foreach (var obj in objects) { if (obj.Value.TypeId == typeId) { if (checker != null) { if (!checker(obj.Value)) continue; } if (obj.Value.StackCount > stackCount) { stackCount = 0; break; } else { stackCount -= obj.Value.StackCount; if (stackCount == 0) break; } } } return stackCount == 0; } } public class ItemRequest { public struct ItemQueryInfo { public ItemQueryInfo(int typeId, int stackCount = 1, Func checker = null) { TypeId = typeId; StackCount = stackCount; Checker = checker; } public int TypeId { get; private set; } public int StackCount { get; private set; } public Func Checker { get; private set; } } ItemQueryInfo[] Requests; public ItemRequest(params ItemQueryInfo[] infos) => Requests = infos; public bool CheckIsEnough(PlayerPackage package) { bool isEnough = true; lock (package.objects) { for (int i = 0; i < Requests.Length; i++) isEnough &= package.CheckItems(Requests[i].TypeId, Requests[i].StackCount, Requests[i].Checker); } return isEnough; } public bool Consume(ConnectionPlayerBase who, PlayerPackage package) { lock (package.objects) { if (CheckIsEnough(package)) { for (int i = 0; i < Requests.Length; i++) package.DeleteItemWithType(who, Requests[i].TypeId, Requests[i].StackCount, Requests[i].Checker); return true; } return false; } } } public void OnClientReceivedModify(Item changedItem, out Item added) { lock (objects) { if (objects.TryGetValue(changedItem.InstanceId, out Item item)) { item.ClientApplyChange(changedItem); added = null; } else { objects.Add(changedItem.InstanceId, changedItem); added = changedItem; } } } public void OnClientReceivedDelete(int itemInstanceId) { lock (objects) { if (objects.ContainsKey(itemInstanceId)) objects.Remove(itemInstanceId); } } public void ReadFromData(DataStorage data) { data.Read(out idAllocator); data.Read(out objects); data.ReadUncheck(out maxItemSize); } public void WriteToData(DataStorage data) { data.Write(idAllocator); data.Write(objects); data.WriteUncheck(maxItemSize); } } }