public override void Handle(GameSession session, PacketReader packet) { int itemId = packet.ReadInt(); packet.ReadShort(); // Unknown int amount = packet.ReadInt(); BoxType boxType = (BoxType)packet.ReadShort(); string functionName = ItemMetadataStorage.GetFunction(itemId).Name; if (functionName != "SelectItemBox" && functionName != "OpenItemBox") { return; } Dictionary <long, Item> items = new Dictionary <long, Item>(session.Player.Inventory.Items.Where(x => x.Value.Id == itemId)); // Make copy of items in-case new item is added if (items.Count == 0) { return; } int index = 0; if (boxType == BoxType.SELECT) { index = packet.ReadShort() - 0x30; // Starts at 0x30 for some reason if (index < 0) { return; } SelectItemBox selectBox = ItemMetadataStorage.GetFunction(itemId).SelectItemBox; HandleSelectBox(session, items, selectBox, index, amount); return; } OpenItemBox openBox = ItemMetadataStorage.GetFunction(itemId).OpenItemBox; HandleOpenBox(session, items, /*openBox,*/ amount); }
public static void GiveItemFromOpenBox(GameSession session, Item item) { OpenItemBox box = item.Function.OpenItemBox; ItemDropMetadata metadata = ItemDropMetadataStorage.GetItemDropMetadata(box.BoxId); if (metadata == null) { session.Send(NoticePacket.Notice("No items found", NoticeType.Chat)); return; } if (box.AmountRequired > item.Amount) { return; } Inventory inventory = session.Player.Inventory; if (box.RequiredItemId > 0) { Item requiredItem = inventory.Items[box.RequiredItemId]; if (requiredItem == null) { return; } inventory.ConsumeItem(session, requiredItem.Uid, 1); } inventory.ConsumeItem(session, item.Uid, box.AmountRequired); Random rng = RandomProvider.Get(); // Receive one item from each drop group if (box.ReceiveOneItem) { foreach (DropGroup group in metadata.DropGroups) { //randomize the contents List <DropGroupContent> contentList = group.Contents.OrderBy(x => rng.Next()).ToList(); foreach (DropGroupContent dropContent in contentList) { List <Item> items = GetItemsFromDropGroup(dropContent, session.Player.Gender, session.Player.Job); foreach (Item newItem in items) { inventory.AddItem(session, newItem, true); } } } return; } // receive all items from each drop group foreach (DropGroup group in metadata.DropGroups) { foreach (DropGroupContent dropContent in group.Contents) { List <Item> items = GetItemsFromDropGroup(dropContent, session.Player.Gender, session.Player.Job); foreach (Item newItem in items) { inventory.AddItem(session, newItem, true); } } } }
public static bool GiveItemFromOpenBox(GameSession session, Item item, out OpenBoxResult boxResult) { boxResult = OpenBoxResult.Success; OpenItemBox box = item.Function.OpenItemBox; ItemDropMetadata metadata = ItemDropMetadataStorage.GetItemDropMetadata(box.BoxId); if (metadata == null) { session.Send(NoticePacket.Notice("No items found", NoticeType.Chat)); boxResult = OpenBoxResult.UnableToOpen; return(false); } if (box.AmountRequired > item.Amount) { boxResult = OpenBoxResult.UnableToOpen; return(false); } IInventory inventory = session.Player.Inventory; List <Item> rewards = new(); // Receive one item from each drop group if (box.ReceiveOneItem) { foreach (DropGroup group in metadata.DropGroups) { bool receivedItem = false; // Randomize the contents IOrderedEnumerable <DropGroupContent> dropContent = group.Contents.OrderBy(_ => Random.Shared.Next()); foreach (DropGroupContent content in dropContent) { // If player has already received an item from this group, skip other contents if (box.ReceiveOneItem && receivedItem) { continue; } List <Item> items = GetItemsFromDropGroup(content, session.Player, item); foreach (Item newItem in items) { receivedItem = true; rewards.Add(newItem); } } } } else { // receive all items from each drop group foreach (DropGroup group in metadata.DropGroups) { foreach (DropGroupContent dropContent in group.Contents) { List <Item> items = GetItemsFromDropGroup(dropContent, session.Player, item); rewards.AddRange(items); } } } // Check if any inventory of the rewards is full if (rewards.Any(reward => inventory.GetFreeSlots(reward.InventoryTab) <= 0)) { boxResult = OpenBoxResult.InventoryFull; return(false); } // Remove the box and required items if (box.RequiredItemId > 0) { Item requiredItem = inventory.GetByUid(box.RequiredItemId); if (requiredItem is null) { boxResult = OpenBoxResult.UnableToOpen; return(false); } inventory.ConsumeItem(session, requiredItem.Uid, 1); } inventory.ConsumeItem(session, item.Uid, box.AmountRequired); // give the rewards foreach (Item reward in rewards) { reward.Uid = DatabaseManager.Items.Insert(reward); if (inventory.CanHold(reward)) { inventory.AddItem(session, reward, true); continue; } boxResult = OpenBoxResult.InventoryFull; MailHelper.InventoryWasFull(reward, session.Player.CharacterId); } return(true); }
protected override List <ItemMetadata> Parse() { // Item breaking ingredients Dictionary <int, List <ItemBreakReward> > rewards = new Dictionary <int, List <ItemBreakReward> >(); foreach (PackFileEntry entry in Resources.XmlReader.Files) { if (!entry.Name.StartsWith("table/itembreakingredient")) { continue; } XmlDocument innerDocument = Resources.XmlReader.GetXmlDocument(entry); XmlNodeList individualItems = innerDocument.SelectNodes($"/ms2/item"); foreach (XmlNode nodes in individualItems) { string locale = nodes.Attributes["locale"]?.Value ?? ""; if (locale != "NA" && locale != "") { continue; } int itemID = int.Parse(nodes.Attributes["ItemID"].Value); rewards[itemID] = new List <ItemBreakReward>(); int ingredientItemID1 = int.Parse(nodes.Attributes["IngredientItemID1"]?.Value ?? "0"); int ingredientCount1 = int.Parse(nodes.Attributes["IngredientCount1"]?.Value ?? "0"); rewards[itemID].Add(new ItemBreakReward(ingredientItemID1, ingredientCount1)); _ = int.TryParse(nodes.Attributes["IngredientItemID2"]?.Value ?? "0", out int ingredientItemID2); _ = int.TryParse(nodes.Attributes["IngredientCount2"]?.Value ?? "0", out int ingredientCount2); rewards[itemID].Add(new ItemBreakReward(ingredientItemID2, ingredientCount2)); _ = int.TryParse(nodes.Attributes["IngredientItemID3"]?.Value ?? "0", out int ingredientItemID3); _ = int.TryParse(nodes.Attributes["IngredientCount3"]?.Value ?? "0", out int ingredientCount3); rewards[itemID].Add(new ItemBreakReward(ingredientItemID3, ingredientCount3)); } } // Item rarity Dictionary <int, int> rarities = new Dictionary <int, int>(); foreach (PackFileEntry entry in Resources.XmlReader.Files) { if (!entry.Name.StartsWith("table/na/itemwebfinder")) { continue; } XmlDocument innerDocument = Resources.XmlReader.GetXmlDocument(entry); XmlNodeList nodes = innerDocument.SelectNodes($"/ms2/key"); foreach (XmlNode node in nodes) { int itemId = int.Parse(node.Attributes["id"].Value); int rarity = int.Parse(node.Attributes["grade"].Value); rarities[itemId] = rarity; } } // Items List <ItemMetadata> items = new List <ItemMetadata>(); foreach (PackFileEntry entry in Resources.XmlReader.Files) { if (!entry.Name.StartsWith("item/")) { continue; } ItemMetadata metadata = new ItemMetadata(); string filename = Path.GetFileNameWithoutExtension(entry.Name); int itemId = int.Parse(filename); if (items.Exists(item => item.Id == itemId)) { continue; } metadata.Id = itemId; Debug.Assert(metadata.Id > 0, $"Invalid Id {metadata.Id} from {itemId}"); // Parse XML XmlDocument document = Resources.XmlReader.GetXmlDocument(entry); XmlNode item = document.SelectSingleNode("ms2/environment"); // Tag XmlNode basic = item.SelectSingleNode("basic"); metadata.Tag = basic.Attributes["stringTag"].Value; // Gear/Cosmetic slot XmlNode slots = item.SelectSingleNode("slots"); XmlNode slot = slots.FirstChild; bool slotResult = Enum.TryParse(slot.Attributes["name"].Value, out metadata.Slot); if (!slotResult && !string.IsNullOrEmpty(slot.Attributes["name"].Value)) { Console.WriteLine($"Failed to parse item slot for {itemId}: {slot.Attributes["name"].Value}"); } int totalSlots = slots.SelectNodes("slot").Count; if (totalSlots > 1) { if (metadata.Slot == ItemSlot.CL || metadata.Slot == ItemSlot.PA) { metadata.IsDress = true; } else if (metadata.Slot == ItemSlot.RH || metadata.Slot == ItemSlot.LH) { metadata.IsTwoHand = true; } } // Hair data if (slot.Attributes["name"].Value == "HR") { int assetNodeCount = slot.SelectNodes("asset").Count; XmlNode asset = slot.FirstChild; XmlNode scaleNode = slot.SelectSingleNode("scale"); if (assetNodeCount == 3) // This hair has a front and back positionable hair { XmlNode backHair = asset.NextSibling; // back hair info XmlNode frontHair = backHair.NextSibling; // front hair info int backHairNodes = backHair.SelectNodes("custom").Count; CoordF[] bPosCord = new CoordF[backHairNodes]; CoordF[] bPosRotation = new CoordF[backHairNodes]; CoordF[] fPosCord = new CoordF[backHairNodes]; CoordF[] fPosRotation = new CoordF[backHairNodes]; for (int i = 0; i < backHairNodes; i++) { foreach (XmlNode backPresets in backHair) { if (backPresets.Name == "custom") { bPosCord[i] = CoordF.Parse(backPresets.Attributes["position"].Value); bPosRotation[i] = CoordF.Parse(backPresets.Attributes["rotation"].Value); } } foreach (XmlNode frontPresets in frontHair) { if (frontPresets.Name == "custom") { fPosCord[i] = CoordF.Parse(frontPresets.Attributes["position"].Value); fPosRotation[i] = CoordF.Parse(frontPresets.Attributes["position"].Value); } } HairPresets hairPresets = new HairPresets() { }; hairPresets.BackPositionCoord = bPosCord[i]; hairPresets.BackPositionRotation = bPosRotation[i]; hairPresets.FrontPositionCoord = fPosCord[i]; hairPresets.FrontPositionRotation = fPosRotation[i]; hairPresets.MinScale = float.Parse(scaleNode?.Attributes["min"]?.Value ?? "0"); hairPresets.MaxScale = float.Parse(scaleNode?.Attributes["max"]?.Value ?? "0"); metadata.HairPresets.Add(hairPresets); } } else if (assetNodeCount == 2) // This hair only has back positionable hair { XmlNode backHair = asset.NextSibling; // back hair info int backHairNodes = backHair.SelectNodes("custom").Count; CoordF[] bPosCord = new CoordF[backHairNodes]; CoordF[] bPosRotation = new CoordF[backHairNodes]; for (int i = 0; i < backHairNodes; i++) { foreach (XmlNode backPresets in backHair) { if (backPresets.Name == "custom") { bPosCord[i] = CoordF.Parse(backPresets.Attributes["position"].Value); bPosRotation[i] = CoordF.Parse(backPresets.Attributes["rotation"].Value); } } HairPresets hairPresets = new HairPresets() { }; hairPresets.BackPositionCoord = bPosCord[i]; hairPresets.BackPositionRotation = bPosRotation[i]; hairPresets.FrontPositionCoord = CoordF.Parse("0, 0, 0"); hairPresets.FrontPositionRotation = CoordF.Parse("0, 0, 0"); hairPresets.MinScale = float.Parse(scaleNode?.Attributes["min"]?.Value ?? "0"); hairPresets.MaxScale = float.Parse(scaleNode?.Attributes["max"]?.Value ?? "0"); metadata.HairPresets.Add(hairPresets); } } else // hair does not have back or front positionable hair { HairPresets hairPresets = new HairPresets() { }; hairPresets.BackPositionCoord = CoordF.Parse("0, 0, 0"); hairPresets.BackPositionRotation = CoordF.Parse("0, 0, 0"); hairPresets.FrontPositionCoord = CoordF.Parse("0, 0, 0"); hairPresets.FrontPositionRotation = CoordF.Parse("0, 0, 0"); hairPresets.MinScale = float.Parse(scaleNode?.Attributes["min"]?.Value ?? "0"); hairPresets.MaxScale = float.Parse(scaleNode?.Attributes["max"]?.Value ?? "0"); metadata.HairPresets.Add(hairPresets); } } // Color data XmlNode customize = item.SelectSingleNode("customize"); metadata.ColorIndex = int.Parse(customize.Attributes["defaultColorIndex"].Value); metadata.ColorPalette = int.Parse(customize.Attributes["colorPalette"].Value); // Badge slot XmlNode gem = item.SelectSingleNode("gem"); bool gemResult = Enum.TryParse(gem.Attributes["system"].Value, out metadata.Gem); if (!gemResult && !string.IsNullOrEmpty(gem.Attributes["system"].Value)) { Console.WriteLine($"Failed to parse badge slot for {itemId}: {gem.Attributes["system"].Value}"); } // Inventory tab and max stack size XmlNode property = item.SelectSingleNode("property"); try { byte type = byte.Parse(property.Attributes["type"].Value); byte subType = byte.Parse(property.Attributes["subtype"].Value); bool skin = byte.Parse(property.Attributes["skin"].Value) != 0; metadata.Tab = GetTab(type, subType, skin); metadata.IsTemplate = byte.Parse(property.Attributes["skinType"]?.Value ?? "0") == 99; metadata.TradeableCount = byte.Parse(property.Attributes["tradableCount"].Value); metadata.RepackageCount = byte.Parse(property.Attributes["rePackingLimitCount"].Value); metadata.RepackageItemConsumeCount = byte.Parse(property.Attributes["rePackingItemConsumeCount"].Value); // sales price XmlNode sell = property.SelectSingleNode("sell"); metadata.SellPrice = sell.Attributes["price"]?.Value.Split(',').Select(int.Parse).ToList() ?? null; metadata.SellPriceCustom = sell.Attributes["priceCustom"]?.Value.Split(',').Select(int.Parse).ToList() ?? null; } catch (Exception e) { Console.WriteLine($"Failed to parse tab slot for {itemId}: {e.Message}"); } metadata.StackLimit = int.Parse(property.Attributes["slotMax"].Value); // Rarity XmlNode option = item.SelectSingleNode("option"); metadata.OptionStatic = int.Parse(option.Attributes["static"].Value); metadata.OptionRandom = int.Parse(option.Attributes["random"].Value); metadata.OptionConstant = int.Parse(option.Attributes["constant"].Value); metadata.OptionLevelFactor = int.Parse(option.Attributes["optionLevelFactor"].Value); XmlNode function = item.SelectSingleNode("function"); string contentType = function.Attributes["name"].Value; metadata.FunctionData.Name = contentType; // Item boxes if (contentType == "OpenItemBox") { // selection boxes are SelectItemBox and 1,boxid // normal boxes are OpenItemBox and 0,1,0,boxid // fragments are OpenItemBox and 0,1,0,boxid,required_amount if (function.Attributes["parameter"].Value.Contains('l')) { continue; // TODO: Implement these CN items. Skipping for now } List <string> parameters = new List <string>(function.Attributes["parameter"].Value.Split(",")); OpenItemBox box = new OpenItemBox(); box.RequiredItemId = int.Parse(parameters[0]); box.ReceiveOneItem = parameters[1] == "1"; box.BoxId = int.Parse(parameters[3]); box.AmountRequired = 1; if (parameters.Count == 5) { box.AmountRequired = int.Parse(parameters[4]); } metadata.FunctionData.OpenItemBox = box; } else if (contentType == "SelectItemBox") { if (function.Attributes["parameter"].Value.Contains('l')) { continue; // TODO: Implement these CN items. Skipping for now } List <string> parameters = new List <string>(function.Attributes["parameter"].Value.Split(",")); parameters.RemoveAll(param => param.Length == 0); SelectItemBox box = new SelectItemBox(); box.GroupId = int.Parse(parameters[0]); box.BoxId = int.Parse(parameters[1]); metadata.FunctionData.SelectItemBox = box; } else if (contentType == "ChatEmoticonAdd") { ChatEmoticonAdd sticker = new ChatEmoticonAdd(); string rawParameter = function.Attributes["parameter"].Value; string decodedParameter = HttpUtility.HtmlDecode(rawParameter); XmlDocument xmlParameter = new XmlDocument(); xmlParameter.LoadXml(decodedParameter); XmlNode functionParameters = xmlParameter.SelectSingleNode("v"); sticker.Id = byte.Parse(functionParameters.Attributes["id"].Value); sticker.Duration = int.Parse(functionParameters.Attributes["durationSec"]?.Value ?? "0"); metadata.FunctionData.ChatEmoticonAdd = sticker; } else if (contentType == "OpenMassive") { OpenMassiveEvent massiveEvent = new OpenMassiveEvent(); string rawParameter = function.Attributes["parameter"].Value; string cleanParameter = rawParameter.Remove(1, 1); // remove the unwanted space string decodedParameter = HttpUtility.HtmlDecode(cleanParameter); XmlDocument xmlParameter = new XmlDocument(); xmlParameter.LoadXml(decodedParameter); XmlNode functionParameters = xmlParameter.SelectSingleNode("v"); massiveEvent.FieldId = int.Parse(functionParameters.Attributes["fieldID"].Value); massiveEvent.Duration = int.Parse(functionParameters.Attributes["portalDurationTick"].Value); massiveEvent.Capacity = byte.Parse(functionParameters.Attributes["maxCount"].Value); metadata.FunctionData.OpenMassiveEvent = massiveEvent; } else if (contentType == "LevelPotion") { LevelPotion levelPotion = new LevelPotion(); string rawParameter = function.Attributes["parameter"].Value; string decodedParameter = HttpUtility.HtmlDecode(rawParameter); XmlDocument xmlParameter = new XmlDocument(); xmlParameter.LoadXml(decodedParameter); XmlNode functionParameters = xmlParameter.SelectSingleNode("v"); levelPotion.TargetLevel = byte.Parse(functionParameters.Attributes["targetLevel"].Value); metadata.FunctionData.LevelPotion = levelPotion; } else if (contentType == "VIPCoupon") { VIPCoupon coupon = new VIPCoupon(); string rawParameter = function.Attributes["parameter"].Value; string decodedParameter = HttpUtility.HtmlDecode(rawParameter); XmlDocument xmlParameter = new XmlDocument(); xmlParameter.LoadXml(decodedParameter); XmlNode functionParameters = xmlParameter.SelectSingleNode("v"); coupon.Duration = int.Parse(functionParameters.Attributes["period"].Value); metadata.FunctionData.VIPCoupon = coupon; } else if (contentType == "HongBao") { HongBaoData hongBao = new HongBaoData(); string rawParameter = function.Attributes["parameter"].Value; string decodedParameter = HttpUtility.HtmlDecode(rawParameter); XmlDocument xmlParameter = new XmlDocument(); xmlParameter.LoadXml(decodedParameter); XmlNode functionParameters = xmlParameter.SelectSingleNode("v"); hongBao.Id = int.Parse(functionParameters.Attributes["itemId"].Value); hongBao.Count = short.Parse(functionParameters.Attributes["totalCount"].Value); hongBao.TotalUsers = byte.Parse(functionParameters.Attributes["totalUser"].Value); hongBao.Duration = int.Parse(functionParameters.Attributes["durationSec"].Value); metadata.FunctionData.HongBao = hongBao; } else if (contentType == "SuperWorldChat") { string[] parameters = function.Attributes["parameter"].Value.Split(","); metadata.FunctionData.Id = int.Parse(parameters[0]); // only storing the first parameter. Not sure if the server uses the other 2. } else if (contentType == "OpenGachaBox") { string[] parameters = function.Attributes["parameter"].Value.Split(","); metadata.FunctionData.Id = int.Parse(parameters[0]); // only storing the first parameter. Unknown what the second parameter is used for. } else if (contentType == "OpenCoupleEffectBox") { OpenCoupleEffectBox box = new OpenCoupleEffectBox(); string[] parameters = function.Attributes["parameter"].Value.Split(","); box.Id = int.Parse(parameters[0]); box.Rarity = byte.Parse(parameters[1]); metadata.FunctionData.OpenCoupleEffectBox = box; } else if (contentType == "InstallBillBoard") { InstallBillboard balloon = new InstallBillboard(); string rawParameter = function.Attributes["parameter"].Value; string decodedParameter = HttpUtility.HtmlDecode(rawParameter); XmlDocument xmlParameter = new XmlDocument(); xmlParameter.LoadXml(decodedParameter); XmlNode functionParameters = xmlParameter.SelectSingleNode("v"); balloon.InteractId = int.Parse(functionParameters.Attributes["interactID"].Value); balloon.Duration = int.Parse(functionParameters.Attributes["durationSec"].Value); balloon.Model = functionParameters.Attributes["model"].Value; balloon.Asset = functionParameters.Attributes["asset"]?.Value ?? ""; balloon.NormalState = functionParameters.Attributes["normal"].Value; balloon.Reactable = functionParameters.Attributes["reactable"].Value; balloon.Scale = float.Parse(functionParameters.Attributes["scale"]?.Value ?? "0"); metadata.FunctionData.InstallBillboard = balloon; } else if (contentType == "TitleScroll" || contentType == "ItemExchangeScroll" || contentType == "OpenInstrument" || contentType == "StoryBook" || contentType == "FishingRod" || contentType == "ItemChangeBeauty" || contentType == "ItemRePackingScroll") { metadata.FunctionData.Id = int.Parse(function.Attributes["parameter"].Value); } // Music score charges XmlNode musicScore = item.SelectSingleNode("MusicScore"); metadata.PlayCount = int.Parse(musicScore.Attributes["playCount"].Value); metadata.FileName = musicScore.Attributes["fileName"].Value; metadata.IsCustomScore = bool.Parse(musicScore.Attributes["isCustomNote"].Value); // Shop ID from currency items if (item["Shop"] != null) { XmlNode shop = item.SelectSingleNode("Shop"); metadata.ShopID = int.Parse(shop.Attributes["systemShopID"].Value); } XmlNode skill = item.SelectSingleNode("skill"); metadata.SkillID = int.Parse(skill.Attributes["skillID"].Value); XmlNode limit = item.SelectSingleNode("limit"); metadata.EnableBreak = byte.Parse(limit.Attributes["enableBreak"].Value) == 1; metadata.Level = int.Parse(limit.Attributes["levelLimit"].Value); metadata.TransferType = (TransferType)byte.Parse(limit.Attributes["transferType"].Value); metadata.Sellable = byte.Parse(limit.Attributes["shopSell"].Value) == 1; metadata.RecommendJobs = limit.Attributes["recommendJobs"]?.Value.Split(",").Where(x => !string.IsNullOrEmpty(x)).Select(int.Parse).ToList(); metadata.Gender = byte.Parse(limit.Attributes["genderLimit"].Value); XmlNode installNode = item.SelectSingleNode("install"); metadata.IsCubeSolid = byte.Parse(installNode.Attributes["cubeProp"].Value) == 1; metadata.ObjectId = int.Parse(installNode.Attributes["objCode"].Value); XmlNode housingNode = item.SelectSingleNode("housing"); string value = housingNode.Attributes["categoryTag"]?.Value; if (value is not null) { List <string> categories = new List <string>(value.Split(",")); _ = short.TryParse(categories[0], out short category); metadata.HousingCategory = (ItemHousingCategory)category; } // Item breaking ingredients if (rewards.ContainsKey(itemId)) { metadata.BreakRewards = rewards[itemId]; } if (rarities.ContainsKey(itemId)) { metadata.Rarity = rarities[itemId]; } items.Add(metadata); } return(items); }