/// <summary> /// Sets a networked variable of the script. /// </summary> /// <param name="name">Name of the variable to store.</param> /// <param name="value">Value of the variable to store.</param> public void SetNetworkVar(string name, object value) { // Set the variable. this.SetVar(name, value); if (this._variableChangeTransactions.ContainsKey(Thread.CurrentThread)) { // Add to the transaction. PrepareDictionary(name, value, this._variableChangeTransactions[Thread.CurrentThread]); } else { // Create the LDF. var ldf = new LegoDataDictionary(); PrepareDictionary(name, value, ldf); // Broadcast the variable changes. Task.Run(() => { this.Zone.BroadcastMessage(new ScriptNetworkVarUpdateMessage { Associate = this.GameObject, Data = ldf, }); }); } }
public override void Deserialize(BitReader reader) { base.Deserialize(reader); Configs = new LegoDataDictionary(); Configs.DeserializePathConfigs(reader); }
public async Task AddItemAsync(Lot lot, uint count, LegoDataDictionary extraInfo = default) { await using var cdClient = new CdClientContext(); var componentId = await cdClient.ComponentsRegistryTable.FirstOrDefaultAsync( r => r.Id == lot && r.Componenttype == (int)ComponentId.ItemComponent ); if (componentId == default) { Logger.Error($"{lot} does not have a Item component"); return; } var component = await cdClient.ItemComponentTable.FirstOrDefaultAsync( i => i.Id == componentId.Componentid ); if (component == default) { Logger.Error( $"{lot} has a corrupted component registry. There is no Item component of Id: {componentId.Componentid}" ); return; } Debug.Assert(component.ItemType != null, "component.ItemType != null"); await AddItemAsync(lot, count, ((ItemType)component.ItemType).GetInventoryType(), extraInfo); }
/// <summary> /// Build a LegoDataDictionary from path config values. Path configs have a different serialization than LDF in /// Lvl files has. /// </summary> /// <remarks> /// Some config entries have no type defined, for example `pathspeed=0.8` or `delay=5`. /// This code tries to interpret those values first as an int, then as a float, and if /// neither succeeds, stores it as a string. /// </remarks> /// <seealso cref="SerializePathConfigs"/> public static void DeserializePathConfigs(this LegoDataDictionary @this, BitReader reader) { var configCount = reader.Read <uint>(); for (var i = 0; i < configCount; i++) { var key = reader.ReadNiString(true, true); var typeAndValue = reader.ReadNiString(true, true); var firstColon = typeAndValue.IndexOf(':'); if (firstColon == -1) { if (int.TryParse(typeAndValue, out var intValue)) { @this.Add(key, intValue); } else if (float.TryParse(typeAndValue, out var floatVal)) { @this.Add(key, floatVal); } else { @this.Add(key, typeAndValue); } continue; } var type = int.Parse(typeAndValue[..firstColon]);
/// <summary> /// Instantiates an item using saved information from the player (e.g. the count, slot and LDD) /// </summary> /// <param name="itemInstance">An item as fetched from the Uchu database to base this item on</param> /// <param name="owner">The owner (generally player) of this item</param> /// <param name="inventory">The inventory to add the item to</param> /// <returns>The instantiated item</returns> public static async Task <Item> Instantiate(InventoryItem itemInstance, GameObject owner, Inventory inventory) { return(await Instantiate(owner, itemInstance.Lot, inventory, (uint)itemInstance.Count, (uint)itemInstance.Slot, LegoDataDictionary.FromString(itemInstance.ExtraInfo), (ObjectId)itemInstance.Id, isEquipped : itemInstance.IsEquipped, isBound : itemInstance.IsBound, rootItem : inventory.ManagerComponent.Items.FirstOrDefault(i => i.Id == itemInstance.ParentId))); }
public override void Deserialize(BitReader reader) { base.Deserialize(reader); Rotation = reader.ReadNiQuaternion(); Configs = new LegoDataDictionary(); Configs.DeserializePathConfigs(reader); }
public override void Deserialize(BitReader reader) { Confirmed = reader.ReadBit(); DeleteItem = reader.ReadBit(); OutSuccess = reader.ReadBit(); if (reader.ReadBit()) { InventoryType = (InventoryType)reader.Read <int>(); } if (reader.ReadBit()) { ItemType = (ItemType)reader.Read <int>(); } var len = reader.Read <uint>(); if (len > 0) { var info = reader.ReadString((int)len, true); ExtraInfo = LegoDataDictionary.FromString(info); } ForceDeletion = reader.ReadBit(); if (reader.ReadBit()) { Source = reader.ReadGameObject(Associate.Zone); } if (reader.ReadBit()) { Item = reader.ReadGameObject <Item>(Associate.Zone); } if (reader.ReadBit()) { Requesting = reader.ReadGameObject(Associate.Zone); } if (reader.ReadBit()) { TotalItems = reader.Read <uint>(); } if (reader.ReadBit()) { SubKey = reader.Read <long>(); } if (reader.ReadBit()) { TradeId = reader.Read <long>(); } }
public override void Serialize(BitWriter writer) { writer.WriteBit(true); writer.Write((uint)Items.Count); foreach (var(_, item) in Items) { writer.Write(item.InventoryItemId); writer.Write(item.LOT); writer.WriteBit(false); var stack = item.Count > 1; writer.WriteBit(stack); if (stack) { writer.Write((uint)item.Count); } var hasSlot = item.Slot != -1; writer.WriteBit(hasSlot); if (hasSlot) { writer.Write((ushort)item.Slot); } var hasInventoryType = item.InventoryType != -1; writer.WriteBit(hasInventoryType); if (hasInventoryType) { writer.Write((uint)item.InventoryType); } var hasExtraData = !string.IsNullOrWhiteSpace(item.ExtraInfo); writer.WriteBit(hasExtraData); if (hasExtraData) { writer.WriteLdfCompressed(LegoDataDictionary.FromString(item.ExtraInfo)); } writer.WriteBit(true); } writer.WriteBit(true); writer.Write <uint>(0); }
/// <summary> /// Launches the client. /// </summary> /// <param name="host">Host to launch.</param> /// <returns>Process that was started.</returns> public Process Launch(ServerEntry host) { // Set up the runtime if it isn't installed. if (!this.Runtime.IsInstalled) { if (this.Runtime.CanInstall) { // Install the runtime. this.Runtime.Install(); } else { // Stop the launch if a valid runtime isn't set up. return(null); } } // Modify the boot file. var bootConfigLocation = Path.Combine(this.systemInfo.ClientLocation, "boot.cfg"); LegoDataDictionary bootConfig = null; try { bootConfig = LegoDataDictionary.FromString(File.ReadAllText(bootConfigLocation).Trim().Replace("\n", ""), ','); } catch (FormatException) { bootConfig = LegoDataDictionary.FromString(File.ReadAllText(Path.Combine(this.systemInfo.ClientLocation, "boot_backup.cfg")).Trim().Replace("\n", ""), ','); } bootConfig["SERVERNAME"] = host.ServerName; bootConfig["AUTHSERVERIP"] = host.ServerAddress; File.WriteAllText(bootConfigLocation, bootConfig.ToString(",")); // Apply any pre-launch patches. foreach (var patch in Patcher.Patches) { if (patch is IPreLaunchPatch preLaunchPatch) { if (!patch.Installed) { continue; } preLaunchPatch.OnClientRequestLaunch(); } } // Launch the client. var clientProcess = this.Runtime.RunApplication(Path.Combine(this.systemInfo.ClientLocation, "legouniverse.exe"), this.systemInfo.ClientLocation); clientProcess.Start(); // Return the output. return(clientProcess); }
public override void Deserialize(BitReader reader) { Type = reader.Read <int>(); Value = reader.Read <int>(); Activator = reader.ReadGameObject(Associate.Zone); var ldl = reader.ReadString((int)reader.Read <uint>(), true); Settings = LegoDataDictionary.FromString(ldl); }
public override void Deserialize(BitReader reader) { base.Deserialize(reader); UnknownQuaternion = reader.ReadNiQuaternion(); if (Version >= 17) { Speed = reader.Read <float>(); } Configs = new LegoDataDictionary(); Configs.DeserializePathConfigs(reader); }
public static Item Instantiate(Lot lot, Inventory inventory, uint count, LegoDataDictionary extraInfo = default) { uint slot = default; for (var index = 0; index < inventory.Size; index++) { if (inventory.Items.All(i => i.Slot != index)) { break; } slot++; } return(Instantiate(lot, inventory, count, slot, extraInfo)); }
private async Task UpdateCountAsync(uint count) { if (count >= Count) { AddCount(count); } else { RemoveCount(count); } if (count > 0) { return; } await using var ctx = new UchuContext(); var item = await ctx.InventoryItems.FirstOrDefaultAsync( i => i.Id == Id ); if (item == default) { return; } if (await IsEquippedAsync()) { await UnEquipAsync(); } ctx.InventoryItems.Remove(item); await ctx.SaveChangesAsync(); // Disassemble item. if (LegoDataDictionary.FromString(item.ExtraInfo).TryGetValue("assemblyPartLOTs", out var list)) { foreach (var part in (LegoDataList)list) { await Inventory.ManagerComponent.AddItemAsync((int)part, 1); } } Destroy(this); }
public async Task FinishBuilding(Lot[] models) { var inventory = GameObject.GetComponent <InventoryManagerComponent>(); foreach (var module in models) { inventory.RemoveItem(module, 1, InventoryType.TemporaryModels); } var model = new LegoDataDictionary { ["assemblyPartLOTs"] = LegoDataList.FromEnumerable(models.Select(s => s.Id)) }; await inventory.AddItemAsync(6416, 1, InventoryType.Models, model); await ConfirmFinish(); }
public static void WriteLdfCompressed(this BitWriter @this, LegoDataDictionary dict) { using var stream = new MemoryStream(); using var temp = new BitWriter(stream); Core.BitWriterExtensions.Write(temp, dict); var buffer = stream.ToArray(); var compressed = Zlib.CompressBytes(buffer); @this.Write((uint)(compressed.Length + 9)); @this.Write <byte>(1); @this.Write((uint)buffer.Length); @this.Write((uint)compressed.Length); Core.BitWriterExtensions.Write(@this, compressed); }
public void Deserialize(BitReader reader) { ObjectId = reader.Read <ulong>(); Lot = reader.Read <int>(); if (LvlVersion >= 0x26) { AssetType = reader.Read <uint>(); } if (LvlVersion >= 0x20) { UnknownInt = reader.Read <uint>(); } Position = reader.Read <Vector3>(); Rotation = reader.ReadNiQuaternion(); Scale = reader.Read <float>(); var legoInfo = reader.ReadNiString(true); if (legoInfo.Length > 0) { try { LegoInfo = LegoDataDictionary.FromString(legoInfo); } catch (Exception e) { Console.WriteLine(legoInfo); Console.WriteLine(e); throw; } } if (LvlVersion >= 0x7) { UnknownInt1 = reader.Read <uint>(); } }
public async Task SetCountSilentAsync(uint count) { await using var ctx = new UchuContext(); if (count > ItemComponent.StackSize && ItemComponent.StackSize > 0) { Logger.Error( $"Trying to set {Lot} count to {Count}, this is beyond the item's stack-size; Setting it to stack-size" ); count = (uint)ItemComponent.StackSize; } var item = await ctx.InventoryItems.FirstAsync(i => i.Id == Id); item.Count = count; Logger.Debug($"Setting {this}'s stack size to {item.Count}"); if (count <= 0) { if (await IsEquippedAsync()) { await UnEquipAsync(); } ctx.InventoryItems.Remove(item); Destroy(this); // Disassemble item. if (LegoDataDictionary.FromString(item.ExtraInfo).TryGetValue("assemblyPartLOTs", out var list)) { foreach (var part in (LegoDataList)list) { await Inventory.ManagerComponent.AddItemAsync((int)part, 1); } } } await ctx.SaveChangesAsync(); }
public static Item Instantiate(long itemId, Inventory inventory) { using var cdClient = new CdClientContext(); using var ctx = new UchuContext(); var item = ctx.InventoryItems.FirstOrDefault( i => i.Id == itemId && i.Character.Id == inventory.ManagerComponent.GameObject.Id ); if (item == default) { Logger.Error($"{itemId} is not an item on {inventory.ManagerComponent.GameObject}"); return(null); } var cdClientObject = cdClient.ObjectsTable.FirstOrDefault( o => o.Id == item.Lot ); var itemRegistryEntry = ((Lot)item.Lot).GetComponentId(ComponentId.ItemComponent); if (cdClientObject == default || itemRegistryEntry == default) { Logger.Error($"{itemId} [{item.Lot}] is not a valid item"); return(null); } var instance = Instantiate <Item> ( inventory.ManagerComponent.Zone, cdClientObject.Name, objectId: itemId, lot: item.Lot ); if (!string.IsNullOrWhiteSpace(item.ExtraInfo)) { instance.Settings = LegoDataDictionary.FromString(item.ExtraInfo); } instance.Inventory = inventory; instance.Player = inventory.ManagerComponent.GameObject as Player; return(instance); }
public void SendCharacterData(Connection connection, Character character) { var info = new LegoDataDictionary { ["accountID"] = character.AccountId, ["objid"] = character.ObjectId, ["template"] = 1, ["editor_enabled"] = true, ["editor_level"] = 9, ["name"] = character.Name, ["position.x"] = -629f, ["position.y"] = 613.4f, ["position.z"] = -30f }; connection.Send(new ClientCreateCharacterPacket { Info = info }); }
public override void Serialize(BitWriter writer) { writer.WriteBit(true); var items = Items.Values.ToArray(); writer.Write((uint)items.Length); foreach (var item in items) { writer.Write(item.Id); writer.Write(item.Lot); writer.WriteBit(false); writer.WriteBit(false); writer.WriteBit(false); writer.WriteBit(false); var info = item.Id.FindItem(); if (info == default) { writer.WriteBit(false); } else { if (writer.Flag(!string.IsNullOrWhiteSpace(info.ExtraInfo))) { writer.WriteLdfCompressed(LegoDataDictionary.FromString(info.ExtraInfo)); } } writer.WriteBit(true); } writer.WriteBit(false); }
/// <summary> /// Prepares an LDF wot use with ScriptNetworkVarUpdateMessage and /// sets the internal LDF data of the script component. /// </summary> /// <param name="name">Name of the variable to store.</param> /// <param name="value">Value of the variable to store.</param> /// <param name="ldf">Dictionary to set up for sending with ScriptNetworkVarUpdateMessage.</param> private void PrepareDictionary(string name, object value, LegoDataDictionary ldf) { // Add the value. if (value is IList list) { // Add the list entries. // These aren't stored in the script. for (var i = 0; i < list.Count; i++) { var subValue = list[i]; var key = name + "." + (i + 1); if (subValue is GameObject gameObject) { ldf.Add(key, gameObject.Id); } else { ldf.Add(key, subValue); } } } else { // Convert the object. var newValue = value; if (value is GameObject gameObject) { newValue = (long)gameObject.Id; } // Store the object. if (this.GameObject.TryGetComponent <LuaScriptComponent>(out var component)) { component.Data ??= new LegoDataDictionary(); component.Data.Add(name, newValue); } ldf.Add(name, newValue); } }
/// <summary> /// Performs and operations between setting the /// boot.cfg and launching the client. This will /// yield launching the client. /// </summary> public void OnClientRequestLaunch() { // Determine the host to check. var bootConfig = LegoDataDictionary.FromString(File.ReadAllText(Path.Combine(this.systemInfo.ClientLocation, "boot.cfg").Replace("\n", "")).Trim(), ','); var host = (string)bootConfig["AUTHSERVERIP"]; Console.WriteLine("Check for TCP/UDP for: " + host); // Assume TCP/UDP if any port is specified. // Even if 1001, the stock client will not connect correctly. if (host.Contains(":")) { var portString = host.Remove(0, host.IndexOf(":", StringComparison.Ordinal) + 1).Trim(); if (int.TryParse(portString, out var port)) { Console.WriteLine("Custom port " + port + " specified. Assuming TCP/UDP."); this.Enable(); return; } } // Try to connect and disconnect from port 21836 (default TCP/UDP port). // Port 1001 is more likely to be used by other applications like games. try { // Enable TCP/UDP after a successful connect and close. var client = new TcpClient(host, 21836); client.Close(); Console.WriteLine("Connection to default TCP/UDP port 21836 successful. Assuming TCP/UDP."); this.Enable(); } catch (Exception) { // Disable TCP/UDP (assume RakNet). Console.WriteLine("Connection to default TCP/UDP port 21836 failed. Assuming not TCP/UDP."); this.Disable(); } }
public async Task AddItemAsync(int lot, uint count, InventoryType inventoryType, LegoDataDictionary extraInfo = default) { var itemCount = count; Detach(() => { OnLotAdded.Invoke(lot, itemCount); }); var inventory = _inventories[inventoryType]; // The math here cannot be executed in parallel await using var cdClient = new CdClientContext(); var componentId = cdClient.ComponentsRegistryTable.FirstOrDefault( r => r.Id == lot && r.Componenttype == (int)ComponentId.ItemComponent ); if (componentId == default) { Logger.Error($"{lot} does not have a Item component"); return; } var component = cdClient.ItemComponentTable.FirstOrDefault( i => i.Id == componentId.Componentid ); if (component == default) { Logger.Error( $"{lot} has a corrupted component registry. There is no Item component of Id: {componentId.Componentid}" ); return; } As <Player>().SendChatMessage($"Calculating for {lot} x {count} [{inventoryType}]", PlayerChatChannel.Normal); var stackSize = component.StackSize ?? 1; // Bricks and alike does not have a stack limit. if (stackSize == default) { stackSize = int.MaxValue; } // // Update quest tasks // var questInventory = GameObject.GetComponent <MissionInventoryComponent>(); for (var i = 0; i < count; i++) { await questInventory.ObtainItemAsync(lot); } // // Fill stacks // lock (_lock) { foreach (var item in inventory.Items.Where(i => i.Lot == lot)) { if (item.Count == stackSize) { continue; } var toAdd = (uint)Min(stackSize, (int)count, (int)(stackSize - item.Count)); item.Count += toAdd; count -= toAdd; if (count <= 0) { return; } } // // Create new stacks // var toCreate = count; while (toCreate != default) { var toAdd = (uint)Min(stackSize, (int)toCreate); var item = Item.Instantiate(lot, inventory, toAdd, extraInfo); Start(item); toCreate -= toAdd; } } }
public void RequestActivitySummaryLeaderboardDataMessageHandler(RequestActivitySummaryLeaderboardDataMessage message, Player player) { using var ctx = new UchuContext(); // Get current year and week number according to ISO 8601 var yearAndWeek = ISOWeek.GetYear(DateTime.Now) * 100 + ISOWeek.GetWeekOfYear(DateTime.Now); // Find leaderboard entries for this activity // If weekly leaderboard is requested, only return results from current week var leaderboardQueryable = ctx.ActivityScores .Where(score => score.Activity == message.GameId && (message.Weekly ? score.Week == yearAndWeek : score.Week == 0)); // Find leaderboard type var activity = ClientCache.Find <Activities>(message.GameId); var leaderboardType = (LeaderboardType)(activity?.LeaderboardType ?? -1); // For some reason, whoever made this in 2011 gave the the NS and NT footraces the // same activity ID. So for footraces, we filter by zone ID to ensure we don't mix // the leaderboards. But this check shouldn't be done for other leaderboards, as // Survival minigames have their leaderboards accessible from multiple zones. if (leaderboardType == LeaderboardType.Footrace) { leaderboardQueryable = leaderboardQueryable.Where(score => score.Zone == (int)player.Zone.ZoneId); } // Order either by time ascending or time descending depending on which kind of activity it is if (leaderboardType == LeaderboardType.Footrace || leaderboardType == LeaderboardType.AvantGardensSurvival || leaderboardType == LeaderboardType.BattleOfNimbusStation) { leaderboardQueryable = leaderboardQueryable.OrderByDescending(score => score.Time); } else { leaderboardQueryable = leaderboardQueryable.OrderBy(score => score.Time); } var leaderboard = leaderboardQueryable.ToList(); // Dictionary <rank, score> // Rank is what the client will show as position on the leaderboard var toSend = new Dictionary <int, ActivityScore>(); switch (message.QueryType) { case QueryType.TopSocial: // TODO: Friends. break; case QueryType.TopAll: // Top 10. for (var i = message.ResultsStart; i < message.ResultsEnd && i < leaderboard.Count; i++) { toSend.Add(i + 1, leaderboard[i]); } break; case QueryType.TopCharacter: // Leaderboard around this player's rank. var playerIndex = leaderboard.FindIndex(score => score.CharacterId == player.Id); // If player is not in leaderboard, return (client will show a friendly message telling the player // to first complete the activity) if (playerIndex == -1) { break; } var availableBefore = playerIndex; var availableAfter = leaderboard.Count - playerIndex; // By default we show 5 scores before this player's, and 4 after (last index isn't included). var includeBefore = 5; var includeAfter = 5; // For every step we can't go before, add one to after includeAfter += Math.Max(0, 5 - availableBefore); // For every step we can't go after, add one to before includeBefore += Math.Max(0, 5 - availableAfter); // Ensure we don't go outside the leaderboard limits var startIndex = Math.Max(0, playerIndex - includeBefore); var stopIndex = Math.Min(leaderboard.Count, playerIndex + includeAfter); for (var i = startIndex; i < stopIndex; i++) { toSend.Add(i + 1, leaderboard[i]); } break; } // "Properly" implementing this odd nested-dictionaries-and-arrays-inside-LDF didn't seem // particularly fun and/or useful; this implementation just does everything needed for leaderboards. var data = new LegoDataDictionary { { "ADO.Result", true }, { "Result.Count", 1 }, { "Result[0].Index", "RowNumber" }, { "Result[0].RowCount", toSend.Count }, }; var index = 0; foreach (var(rank, activityScore) in toSend) { var characterName = ctx.Characters.FirstOrDefault(c => c.Id == activityScore.CharacterId)?.Name ?? "Deleted Character"; data.Add($"Result[0].Row[{index}].CharacterID", activityScore.CharacterId); data.Add($"Result[0].Row[{index}].LastPlayed", activityScore.LastPlayed); data.Add($"Result[0].Row[{index}].NumPlayed", activityScore.NumPlayed); data.Add($"Result[0].Row[{index}].RowNumber", rank); data.Add($"Result[0].Row[{index}].Time", activityScore.Time); data.Add($"Result[0].Row[{index}].Points", activityScore.Points); data.Add($"Result[0].Row[{index}].name", characterName); // TODO: ".Relationship" variable (int). // (AGS client script: if not 0, FoundFriendGuild set to true. Teams?) // data.Add($"Result[0].Row[{index}].Relationship", 0); index++; } player.Message(new SendActivitySummaryLeaderboardDataMessage { Associate = player, GameId = message.GameId, InfoType = (int)message.QueryType, LeaderboardData = data, Throttled = false, Weekly = message.Weekly, }); }
/// <summary> /// Instantiates an item using static information. /// </summary> /// <param name="owner">The owner of this item</param> /// <param name="lot">The lot of this item</param> /// <param name="inventory">The inventory to add the item to, if left empty, this item will be left unmanaged</param> /// <param name="count">The count of the item to add</param> /// <param name="slot">The slot to add the item to</param> /// <param name="extraInfo">Optional LDD to set on the item</param> /// <param name="objectId">Explicit object Id for this item, generally only used for player instance items</param> /// <param name="rootItem">The root item this item is based on</param> /// <param name="isEquipped">Whether the game object has this item equipped or not</param> /// <param name="isBound">Whether the game object has bound this item or not</param> /// <param name="lootType">Where this item came from</param> /// <remarks>Note that <c>Start</c> still needs to be called on the item to be registered properly in the world.</remarks> /// <returns>The instantiated item or <c>null</c> if no slot could be acquired or if the item couldn't be added to the inventory</returns> public static async Task <Item> Instantiate(GameObject owner, Lot lot, Inventory inventory, uint count, uint slot = default, LegoDataDictionary extraInfo = default, ObjectId objectId = default, Item rootItem = default, bool isEquipped = false, bool isBound = false, LootType lootType = LootType.None) { // Try to find the slot at which this item should be inserted if no explicit slot is provided if (inventory != default && slot == default) { try { slot = inventory.ClaimSlot(); } catch (InventoryFullException) { return(null); } } var itemTemplate = (await ClientCache.FindAsync <Core.Client.Objects>(lot)); var itemRegistryEntry = (await ClientCache.FindAllAsync <ComponentsRegistry>(lot)).FirstOrDefault( r => r.Componenttype == (int)ComponentId.ItemComponent ); if (itemRegistryEntry == default) { return(default);
public static Item Instantiate(Lot lot, Inventory inventory, uint count, uint slot, LegoDataDictionary extraInfo = default) { using var cdClient = new CdClientContext(); using var ctx = new UchuContext(); var cdClientObject = cdClient.ObjectsTable.FirstOrDefault( o => o.Id == lot ); var itemRegistryEntry = cdClient.ComponentsRegistryTable.FirstOrDefault( r => r.Id == lot && r.Componenttype == 11 ); if (cdClientObject == default || itemRegistryEntry == default) { Logger.Error($"<new item> [{lot}] is not a valid item"); return(null); } var instance = Instantiate <Item> ( inventory.ManagerComponent.Zone, cdClientObject.Name, objectId: ObjectId.Standalone, lot: lot ); instance.Settings = extraInfo ?? new LegoDataDictionary(); var itemComponent = cdClient.ItemComponentTable.First( i => i.Id == itemRegistryEntry.Componentid ); instance.Inventory = inventory; instance.Player = inventory.ManagerComponent.GameObject as Player; var playerCharacter = ctx.Characters.Include(c => c.Items).First( c => c.Id == inventory.ManagerComponent.GameObject.Id ); var inventoryItem = new InventoryItem { Count = count, InventoryType = (int)inventory.InventoryType, Id = instance.Id, IsBound = itemComponent.IsBOP ?? false, Slot = (int)slot, Lot = lot, ExtraInfo = extraInfo?.ToString() }; playerCharacter.Items.Add(inventoryItem); ctx.SaveChanges(); var message = new AddItemToInventoryMessage { Associate = inventory.ManagerComponent.GameObject, InventoryType = (int)inventory.InventoryType, Delta = count, TotalItems = count, Slot = (int)slot, ItemLot = lot, IsBoundOnEquip = itemComponent.IsBOE ?? false, IsBoundOnPickup = itemComponent.IsBOP ?? false, IsBound = inventoryItem.IsBound, Item = instance, ExtraInfo = extraInfo }; (inventory.ManagerComponent.GameObject as Player)?.Message(message); inventory.ManageItem(instance); return(instance); }