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;
    }
Exemple #3
0
        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);
        }