public long ExpirationTimestamp; // TODO: Remove from field if expired public AdBalloon(string id, int interactId, InteractObjectState state, InteractObjectType type, IFieldObject <Player> owner, InstallBillboard metadata, string title, string description, bool publicHouse) : base(id, interactId, type, state) { Owner = owner.Value; Position = owner.Coord; Rotation = owner.Rotation; Model = metadata.Model; Asset = metadata.Asset; NormalState = metadata.NormalState; Reactable = metadata.Reactable; Scale = metadata.Scale; Title = title; Description = description; PublicHouse = publicHouse; CreationTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds() + Environment.TickCount; ExpirationTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds() + Environment.TickCount + metadata.Duration; }
public readonly long ExpirationTimestamp; // TODO: Remove from field if expired public AdBalloon(string id, int interactId, InteractObjectState state, InteractObjectType type, Player owner, InstallBillboard metadata, string title, string description, bool publicHouse) : base(id, interactId, type, state) { Owner = owner; Model = metadata.Model; Asset = metadata.Asset; NormalState = metadata.NormalState; Reactable = metadata.Reactable; Scale = metadata.Scale; Title = title; Description = description; PublicHouse = publicHouse; CreationTimestamp = TimeInfo.Now() + Environment.TickCount; ExpirationTimestamp = TimeInfo.Now() + Environment.TickCount + metadata.Duration; }
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); }