Example #1
0
        dynamic BuildStatus(Saint.Status sStatus)
        {
            dynamic status = new JObject();

            status.id = sStatus.Key;
            _builder.Localize.Strings((JObject)status, sStatus, "Name");
            _builder.Localize.HtmlStrings((JObject)status, sStatus, "Description");
            status.patch    = PatchDatabase.Get("status", sStatus.Key);
            status.category = sStatus.Category;

            status.canDispel = sStatus.CanDispel;

            // If the status doesn't have an icon, we probably don't want it in our data
            if (sStatus.Icon != null && !sStatus.Icon.Path.EndsWith("000000.tex"))
            {
                status.icon = IconDatabase.EnsureEntry("status", sStatus.Icon);
            }
            else
            {
                return(null);
            }

            _builder.Db.Statuses.Add(status);
            _builder.Db.StatusesById[sStatus.Key] = status;

            return(status);
        }
Example #2
0
 void BeastTribe()
 {
     foreach (var sBeastTribe in _builder.Sheet <Saint.BeastTribe>().Skip(1))
     {
         IconDatabase.EnsureEntry("beast", sBeastTribe.Icon, sBeastTribe.Key);
     }
 }
Example #3
0
        int CustomizeIcon(int startIndex, int length, byte dataKey, dynamic npc, dynamic appearence)
        {
            if (dataKey == 0)
            {
                return(0); // Custom or not specified.
            }
            for (var i = 1; i < length; i++)
            {
                var row = _sCharaMakeCustomize[startIndex + i];
                if ((byte)row[0] == dataKey)
                {
                    var icon     = (ImageFile)row["Icon"];
                    var hintitem = row["HintItem"] as Saint.Item;
                    if (hintitem != null && hintitem.Key != 0)
                    {
                        appearence.hairstyleItem = hintitem.Key;
                        _builder.Db.AddReference(npc, "item", hintitem.Key, false);
                    }
                    return(IconDatabase.EnsureEntry("customize", icon));
                }
            }

            //System.Diagnostics.Debug.WriteLine("{0} has custom hair {1}", (string)npc.name, hairstyle);
            return(0); // Not found - custom.
        }
Example #4
0
        public void Write()
        {
            ItemIconDatabase.WriteUpdates();
            IconDatabase.WriteUpdates();

            foreach (var lang in _languagesCodes)
            {
                CreatePartials(lang);
                WriteCore(lang);

                WriteEquipmentCalculators(lang);
                WriteItems(lang);
                WriteQuests(lang);
                WriteLeves(lang);
                WriteNpcs(lang);
                WriteActions(lang);
                WriteFish(lang);
                WriteNodes(lang);
                WriteAchievements(lang);
                WriteInstances(lang);
                WriteFates(lang);
                WriteMobs(lang);
                WriteBrowsers(lang);

                PatchDatabase.WritePatchLists(this, _update, lang);
            }

            PatchDatabase.WriteMasterPatchList();
        }
Example #5
0
        void ImportQuestEventIcon(dynamic quest, Saint.Quest sQuest)
        {
            var sEventIconType = (Saint.IXivRow)sQuest["EventIconType"];
            var baseIconIndex  = (int)(UInt32)sEventIconType.GetRaw(0);

            // Mark function quests
            if (baseIconIndex == 071340)
            {
                quest.unlocksFunction = 1;
            }

            // Calculate the event icon to record.
            var questIconIndex = 0;

            if (sEventIconType.Key == 4)
            {
                questIconIndex = baseIconIndex;
            }
            else if (sQuest.IsRepeatable)
            {
                questIconIndex = baseIconIndex + 2;
            }
            else
            {
                questIconIndex = baseIconIndex + 1;
            }

            var eventIcon = SaintCoinach.Imaging.IconHelper.GetIcon(sQuest.Sheet.Collection.PackCollection, questIconIndex);

            quest.eventIcon = IconDatabase.EnsureEntry("event", eventIcon);
        }
Example #6
0
        void BuildCraftingActions()
        {
            foreach (var sCraftAction in _builder.Sheet <Saint.CraftAction>())
            {
                if (sCraftAction.Name.ToString() == "")
                {
                    continue;
                }

                if (sCraftAction.ClassJob == null)
                {
                    continue; // HWFIXME: This is for all DoH
                }
                dynamic action = new JObject();
                action.id = sCraftAction.Key;
                _builder.Localize.HtmlStrings((JObject)action, sCraftAction, "Name", "Description");
                action.category = 7; // DoH ability
                action.icon     = IconDatabase.EnsureEntry("action", sCraftAction.Icon);
                action.job      = sCraftAction.ClassJob.Key;
                action.affinity = sCraftAction.ClassJobCategory.Key;
                action.lvl      = sCraftAction.ClassJobLevel;

                if (sCraftAction.Cost > 0)
                {
                    action.resource = "CP";
                    action.cost     = sCraftAction.Cost;
                }

                _builder.Db.Actions.Add(action);
            }
        }
Example #7
0
        public override void Start()
        {
            foreach (var sItem in _builder.ItemsToImport)
            {
                var sCustomizeUnlock = sItem.ItemAction as SaintCoinach.Xiv.ItemActions.CustomizeUnlock;
                if (sCustomizeUnlock == null)
                {
                    continue;
                }

                var sCharaMakeCustomizes = sCustomizeUnlock.CustomizeRows.Where(r => (byte)r[0] != 0).ToArray();
                if (sCharaMakeCustomizes.Length == 0)
                {
                    continue;
                }

                var item = _builder.Db.ItemsById[sItem.Key];
                item.customize = new JArray();

                foreach (var sCharaMakeCustomize in sCharaMakeCustomizes)
                {
                    var icon = (ImageFile)sCharaMakeCustomize["Icon"];
                    var id   = IconDatabase.EnsureEntry("customize", icon);
                    item.customize.Add(id);
                }
            }
        }
Example #8
0
        int GetFishIcon(UInt16 itemIconIndex)
        {
            // Replace 02 icon id with 07, eg. 029046 -> 079046 for fish rubbing image
            var fishIconIndex = itemIconIndex - 20000 + 70000;
            var icon          = IconHelper.GetIcon(_builder.Realm.Packs, fishIconIndex);

            return(IconDatabase.EnsureEntry("fish", icon));
        }
Example #9
0
        dynamic GetStatus(Saint.Status sStatus)
        {
            dynamic status = new JObject();

            status.id   = sStatus.Key;
            status.name = sStatus.Name.ToString();
            status.desc = sStatus.Description.ToString();
            status.icon = IconDatabase.EnsureEntry("status", sStatus.Icon);
            return(status);
        }
Example #10
0
 /// <summary>
 /// Creates the database of scriptable object
 /// </summary>
 void createDatabase()
 {
     nData = (IconDatabase)ScriptableObject.CreateInstance(typeof(IconDatabase));
     if (!System.IO.Directory.Exists("Assets/Resources"))
     {
         AssetDatabase.CreateFolder("Assets", "Resources");
     }
     AssetDatabase.CreateAsset(nData, databasePath);
     AssetDatabase.SaveAssets();
     isDatabaseSet = true;
 }
 static void initDatabase()
 {
     if ((IconDatabase)AssetDatabase.LoadAssetAtPath(databasePath, typeof(IconDatabase)) != null)
     {
         nData         = (IconDatabase)AssetDatabase.LoadAssetAtPath(databasePath, typeof(IconDatabase));
         isDatabaseSet = true;
     }
     else
     {
         isDatabaseSet = false;
     }
 }
Example #12
0
        public override void Start()
        {
            foreach (var sItem in _builder.Sheet <Item>())
            {
                var unlock = sItem.ItemAction as MountUnlock;
                if (unlock == null)
                {
                    continue;
                }

                var sMount = unlock.Mount;
                if (string.IsNullOrEmpty(sMount.Singular.ToString()))
                {
                    continue;
                }

                var sMountTransient = _builder.Sheet("MountTransient")[sMount.Key];

                var item = _builder.Db.ItemsById[sItem.Key];
                item.mount             = new JObject();
                item.mount.name        = Utils.CapitalizeWords(sMount.Singular.ToString());
                item.mount.action      = sMountTransient[0].ToString();
                item.mount.description = sMountTransient[1].ToString();
                item.mount.tooltip     = HtmlStringFormatter.Convert((XivString)sMountTransient[2]);

                // Icons
                var iconIndex = (UInt16)sMount.SourceRow.GetRaw("Icon");
                var icon      = IconHelper.GetIcon(_builder.Realm.Packs, iconIndex);
                item.mount.icon = IconDatabase.EnsureEntry("mount", icon);

                int guideIconIndex = 0;
                if (iconIndex < 8000)
                {
                    guideIconIndex = iconIndex - 4000 + 68000;
                }
                else if (iconIndex >= 8000) // For 4.1 SB mounts.
                {
                    guideIconIndex = iconIndex - 8000 + 77000;
                }

                var guideIcon = IconHelper.GetIcon(_builder.Realm.Packs, guideIconIndex);
                if (guideIcon == null)
                {
                    DatabaseBuilder.PrintLine($"Mount {item.mount.name} #{sItem.Key} has invalid guide icon, skipping.");
                }
                else
                {
                    item.mount.guideIcon = IconDatabase.EnsureEntry("mount", guideIcon);
                }

                item.models = new JArray(Utils.ModelCharaKey(sMount.ModelChara));
            }
        }
 private void ExtractIcons(List <Saint.Item> items)
 {
     foreach (var item in items)
     {
         if (_config.Extract40x)
         {
             IconDatabase.EnsureEntry("item\\40x", item.Icon);
         }
         if (_config.Extract80x)
         {
             IconDatabase.EnsureEntryHQ("item\\t", item.Icon, _realm);
         }
     }
 }
Example #14
0
        void ImportQuestEventIcon(dynamic quest, Saint.Quest sQuest)
        {
            var sEventIconType = (Saint.IXivRow)sQuest["EventIconType"];

            if (sEventIconType == null)
            {
                byte index = (byte)sQuest.GetRaw("EventIconType");
                if (index > 32)
                {
                    index         -= 32;
                    sEventIconType = _builder.Sheet("EventIconType").ElementAt(index);
                }
                else
                {
                    throw new NotImplementedException();
                }
            }
            var baseIconIndex = (int)(UInt32)sEventIconType.GetRaw(0);

            // Mark function quests
            if (baseIconIndex == 071340)
            {
                quest.unlocksFunction = 1;
            }

            // Calculate the event icon to record.
            var questIconIndex = 0;

            if (sEventIconType.Key == 4)
            {
                questIconIndex = baseIconIndex;
            }
            else if (sQuest.IsRepeatable)
            {
                questIconIndex = baseIconIndex + 2;
            }
            else
            {
                questIconIndex = baseIconIndex + 1;
            }

            var eventIcon = SaintCoinach.Imaging.IconHelper.GetIcon(sQuest.Sheet.Collection.PackCollection, questIconIndex);

            quest.eventIcon = IconDatabase.EnsureEntry("event", eventIcon);
        }
Example #15
0
        void BuildJournalGenres()
        {
            foreach (var sJournalGenre in _builder.Sheet <Saint.JournalGenre>())
            {
                dynamic genre = new JObject();
                genre.id   = sJournalGenre.Key;
                genre.name = sJournalGenre.Name.ToString();

                if (sJournalGenre.Icon != null)
                {
                    genre.icon = IconDatabase.EnsureEntry("journal", sJournalGenre.Icon);
                }

                genre.category = sJournalGenre.JournalCategory.Name.ToString();
                genre.section  = sJournalGenre.JournalCategory?.JournalSection?.Name?.ToString() ?? "Other Quests";
                _builder.Db.QuestJournalGenres.Add(genre);
            }
        }
Example #16
0
        int CustomizeIcon(int startIndex, int length, byte dataKey, dynamic npc)
        {
            if (dataKey == 0)
            {
                return(0); // Custom or not specified.
            }
            for (var i = 1; i < length; i++)
            {
                var row = _customize[startIndex + i];
                if ((byte)row[0] == dataKey)
                {
                    var icon = (ImageFile)row["Icon"];
                    return(IconDatabase.EnsureEntry("customize", icon));
                }
            }

            //System.Diagnostics.Debug.WriteLine("{0} has custom hair {1}", (string)npc.name, hairstyle);
            return(0); // Not found - custom.
        }
Example #17
0
        public override void Start()
        {
            BuildDutyRoulette();

            var skippedInstances = new[] { 0, 20015, 20016, 50002, 65000, 30048 };

            // Index conditions.  Some PvP instances have multiple conditions, but it doesn't matter.
            foreach (var sContentFinderCondition in _builder.Sheet <Saint.ContentFinderCondition>())
            {
                if (sContentFinderCondition.Key == 0)
                {
                    continue;
                }

                if (sContentFinderCondition.Content is Saint.InstanceContent sInstanceContent)
                {
                    _builder.ContentFinderConditionByInstanceContent[sInstanceContent] = sContentFinderCondition;
                }
            }

            // todo: add new player bonus currency
            // todo: add weekly restriction stuff?

            foreach (var sInstanceContent in _builder.Sheet <Saint.InstanceContent>())
            {
                if (skippedInstances.Contains(sInstanceContent.Key))
                {
                    continue; // Skip test and fan fest instances.
                }
                // Find entry conditions.
                if (!_builder.ContentFinderConditionByInstanceContent.TryGetValue(sInstanceContent, out var sContentFinderCondition))
                {
                    continue; // Skip unreleased content.
                }
                // Skip some instances.
                switch (sContentFinderCondition.ContentType.Key)
                {
                case 3:     // Guildhests
                case 7:     // Quest Battles
                case 8:     // FATEs
                case 20:    // Novice Hall
                    continue;
                }

                var name = sInstanceContent.Name.ToString();
                if (name == "")
                {
                    continue;
                }

                var sContentFinderConditionTransient = _builder.Sheet("ContentFinderConditionTransient")[sContentFinderCondition.Key];

                dynamic instance = new JObject();
                instance.id = sInstanceContent.Key;
                _builder.Localize.Strings((JObject)instance, sInstanceContent, Utils.SanitizeInstanceName, "Name");
                instance.patch        = PatchDatabase.Get("instance", sInstanceContent.Key);
                instance.categoryIcon = IconDatabase.EnsureEntry("instance/type", sContentFinderCondition.ContentType.Icon);
                _builder.Localize.Column((JObject)instance, sContentFinderCondition.ContentType, "Name", "category",
                                         x => string.IsNullOrEmpty(x) ? Hacks.GetContentTypeNameOverride(sContentFinderCondition.ContentType) : x);

                _builder.Localize.Strings((JObject)instance, sContentFinderConditionTransient, "Description");
                instance.time    = (int)sInstanceContent.TimeLimit.TotalMinutes;
                instance.min_lvl = sContentFinderCondition.RequiredClassJobLevel;

                Hacks.SetInstanceIcon(sContentFinderCondition, instance);

                if (sContentFinderCondition.ContentMemberType.HealersPerParty > 0)
                {
                    instance.healer = sContentFinderCondition.ContentMemberType.HealersPerParty;
                }

                if (sContentFinderCondition.ContentMemberType.TanksPerParty > 0)
                {
                    instance.tank = sContentFinderCondition.ContentMemberType.TanksPerParty;
                }

                if (sContentFinderCondition.ContentMemberType.RangedPerParty > 0)
                {
                    instance.ranged = sContentFinderCondition.ContentMemberType.RangedPerParty;
                }

                if (sContentFinderCondition.ContentMemberType.MeleesPerParty > 0)
                {
                    instance.melee = sContentFinderCondition.ContentMemberType.MeleesPerParty;
                }

                // fixme: find where this went.
                //if (conditions.ContentRoulette.Key > 0)
                //    instance.roulette = conditions.ContentRoulette.Key;

                if (sContentFinderCondition.ClassJobLevelSync > 0)
                {
                    instance.max_lvl = sContentFinderCondition.ClassJobLevelSync;
                }

                if (sContentFinderCondition.RequiredItemLevel > 0)
                {
                    instance.min_ilvl = sContentFinderCondition.RequiredItemLevel;
                }

                if (sContentFinderCondition.ItemLevelSync > 0)
                {
                    instance.max_ilvl = sContentFinderCondition.ItemLevelSync;
                }

                var treasureSet = new HashSet <int>();
                var currency    = new Dictionary <string, int>();

                // Bosses
                var sFights = new List <Saint.InstanceContentData.Fight>();
                if (sInstanceContent.Data.Boss != null)
                {
                    sFights.Add(sInstanceContent.Data.Boss);
                }
                if (sInstanceContent.Data.MidBosses != null)
                {
                    sFights.AddRange(sInstanceContent.Data.MidBosses);
                }

                var fights = new JArray();
                foreach (var sFight in sFights)
                {
                    var bossCurrency = new Dictionary <string, int>();
                    if (sFight.CurrencyA > 0)
                    {
                        currency["ClearA"]     = currency.ContainsKey("ClearA") ? currency["ClearA"] + sFight.CurrencyA : sFight.CurrencyA;
                        bossCurrency["ClearA"] = sFight.CurrencyA;
                    }
                    if (sFight.CurrencyB > 0)
                    {
                        currency["ClearB"]     = currency.ContainsKey("ClearB") ? currency["ClearB"] + sFight.CurrencyB : sFight.CurrencyB;
                        bossCurrency["ClearB"] = sFight.CurrencyB;
                    }
                    if (sFight.CurrencyC > 0)
                    {
                        currency["ClearC"]     = currency.ContainsKey("ClearC") ? currency["ClearC"] + sFight.CurrencyC : sFight.CurrencyC;
                        bossCurrency["ClearC"] = sFight.CurrencyC;
                    }
                    if (sFight.PrimaryBNpcs.Count() == 0 && sFight.Treasures.Count() == 0)
                    {
                        continue;
                    }

                    dynamic fight = new JObject();
                    fights.Add(fight);

                    if (bossCurrency.Count > 0)
                    {
                        fight.currency = CreateCurrencyArray(bossCurrency);
                    }

                    fight.type = (sFight == sInstanceContent.Data.Boss) ? "Boss" : "MidBoss";

                    var fightCoffer = CreateTreasureCoffer(instance, sFight.Treasures, sInstanceContent, treasureSet);
                    if (fightCoffer != null)
                    {
                        fight.coffer = fightCoffer;
                    }

                    var mobs = new JArray();
                    fight.mobs = mobs;

                    foreach (var sBoss in sFight.PrimaryBNpcs)
                    {
                        _builder.InstanceIdsByMobId[sBoss.Key] = sInstanceContent.Key;

                        if (!mobs.Any(b => ((long)b) == sBoss.Key))
                        {
                            _builder.Db.AddReference(instance, "mob", sBoss.Key.ToString(), false);
                            mobs.Add(sBoss.Key);
                        }

                        if (!_builder.ItemDropsByMobId.TryGetValue(sBoss.Key, out var itemIds))
                        {
                            itemIds = new List <int>();
                            _builder.ItemDropsByMobId[sBoss.Key] = itemIds;
                        }

                        foreach (var sTreasureItem in sFight.Treasures.SelectMany(t => t.Items))
                        {
                            if (!itemIds.Contains(sTreasureItem.Key))
                            {
                                itemIds.Add(sTreasureItem.Key);
                            }

                            var item = _builder.Db.ItemsById[sTreasureItem.Key];
                            if (item.drops == null)
                            {
                                item.drops = new JArray();
                            }
                            JArray drops = item.drops;
                            if (!drops.Any(t => ((long)t) == sBoss.Key))
                            {
                                drops.Add(sBoss.Key);
                                _builder.Db.AddReference(instance, "item", sTreasureItem.Key, false);
                                _builder.Db.AddReference(item, "instance", sInstanceContent.Key, true);
                            }
                        }

                        if (sFight.CurrencyA > 0)
                        {
                            _builder.AddBossCurrency(sFight.CurrencyA, _builder.TomestoneIds[0], sBoss.Key);
                        }
                        if (sFight.CurrencyB > 0)
                        {
                            _builder.AddBossCurrency(sFight.CurrencyB, _builder.TomestoneIds[1], sBoss.Key);
                        }
                        if (sFight.CurrencyC > 0)
                        {
                            _builder.AddBossCurrency(sFight.CurrencyC, _builder.TomestoneIds[2], sBoss.Key);
                        }
                    }
                }

                if (fights.Count > 0)
                {
                    instance.fights = fights;
                }

                // Treasures
                var coffers = new JArray();
                if (sInstanceContent.Data.MapTreasures != null)
                {
                    foreach (var sTreasure in sInstanceContent.Data.MapTreasures)
                    {
                        var coffer = CreateTreasureCoffer(instance, new Saint.InstanceContentData.Treasure[] { sTreasure }, sInstanceContent, treasureSet);
                        if (coffer != null)
                        {
                            coffers.Add(coffer);
                        }
                    }

                    if (coffers.Count > 0)
                    {
                        instance.coffers = coffers;
                    }
                }

                // Some items are not referenced by the instance, but by the item itself.
                // This snags them.
                if (_builder.Db.ItemsByInstanceId.TryGetValue(sInstanceContent.Key, out var instanceItems))
                {
                    var otherItemRewards = new JArray();
                    foreach (var item in instanceItems)
                    {
                        int itemId = item.id;
                        if (!treasureSet.Contains(itemId))
                        {
                            otherItemRewards.Add(itemId);
                            _builder.Db.AddReference(instance, "item", itemId, false);
                        }
                    }

                    if (otherItemRewards.Count > 0)
                    {
                        instance.rewards = otherItemRewards;
                    }
                }

                // Currency
                var currencyArray = CreateCurrencyArray(currency);
                if (currencyArray.Count > 0)
                {
                    instance.currency = currencyArray;
                }

                _builder.Db.Instances.Add(instance);
                _builder.Db.InstancesById[sInstanceContent.Key] = instance;
            }
        }
Example #18
0
        public override void Start()
        {
            foreach (var sAchievement in _builder.Sheet <Game.Achievement>())
            {
                if (sAchievement.Key == 0 || sAchievement.AchievementCategory.Key == 0)
                {
                    continue;
                }

                if (sAchievement.AchievementCategory.AchievementKind.Name == "Legacy")
                {
                    continue;
                }

                dynamic achievement = new JObject();
                achievement.id = sAchievement.Key;
                _builder.Localize.Strings((JObject)achievement, sAchievement, "Name", "Description");
                achievement.patch    = PatchDatabase.Get("achievement", sAchievement.Key);
                achievement.points   = sAchievement.Points;
                achievement.category = sAchievement.AchievementCategory.Key;

                if (sAchievement.Title.Key != 0)
                {
                    if (sAchievement.Title.Feminine == sAchievement.Title.Masculine)
                    {
                        achievement.title = sAchievement.Title.Masculine.ToString();
                    }
                    else
                    {
                        achievement.title = sAchievement.Title.ToString();
                    }
                }

                if (sAchievement.Item.Key != 0)
                {
                    achievement.item = sAchievement.Item.Key;
                    var item = _builder.Db.ItemsById[sAchievement.Item.Key];
                    if (item.achievements == null)
                    {
                        item.achievements = new JArray();
                    }
                    item.achievements.Add(sAchievement.Key);
                    _builder.Db.AddReference(achievement, "item", sAchievement.Item.Key, false);
                    _builder.Db.AddReference(item, "achievement", sAchievement.Key, false);
                }

                achievement.icon = IconDatabase.EnsureEntry("achievement", sAchievement.Icon);

                _builder.Db.Achievements.Add(achievement);
            }

            foreach (var sAchievementCategory in _builder.Sheet <Game.AchievementCategory>())
            {
                if (sAchievementCategory.Key == 0 || sAchievementCategory.Name == "")
                {
                    continue;
                }

                dynamic category = new JObject();
                category.id   = sAchievementCategory.Key;
                category.name = sAchievementCategory.Name.ToString();
                category.kind = sAchievementCategory.AchievementKind.Name.ToString();

                _builder.Db.AchievementCategories.Add(category);
            }
        }
Example #19
0
        void BuildAppearanceData()
        {
            foreach (var sNpc in _builder.NpcsToImport)
            {
                var race = (Saint.Race)sNpc.Base["Race"];

                var npc = _builder.GetOrCreateNpc(sNpc);
                if (race == null || race.Key == 0)
                {
                    continue; // Unique or beast NPCs, can't do appearance now.
                }
                dynamic appearance = new JObject();
                npc.appearance = appearance;

                var gender = (byte)sNpc.Base["Gender"];
                var isMale = gender == 0;
                appearance.gender = isMale ? "Male" : "Female";

                appearance.race = isMale ? race.Masculine.ToString() : race.Feminine.ToString();

                var tribe = (Saint.Tribe)sNpc.Base["Tribe"];
                appearance.tribe = isMale ? tribe.Masculine.ToString() : tribe.Feminine.ToString();

                appearance.height = sNpc.Base["Height"];

                var bodyType = (byte)sNpc.Base["BodyType"];
                if (bodyType != 1)
                {
                    appearance.bodyType = GetBodyType(bodyType);
                }

                // Faces
                var baseFace = (byte)sNpc.Base["Face"];
                var face     = baseFace % 100; // Value matches the asset number, % 100 approximate face # nicely.
                appearance.face = face;

                var isValidFace  = face < 8;
                var isCustomFace = baseFace > 7;
                if (isCustomFace)
                {
                    appearance.customFace = 1;
                }

                appearance.jaw = 1 + (byte)sNpc.Base["Jaw"];

                appearance.eyebrows = 1 + (byte)sNpc.Base["Eyebrows"];

                appearance.nose = 1 + (byte)sNpc.Base["Nose"];

                appearance.skinColor     = FormatColorCoordinates((byte)sNpc.Base["SkinColor"]);
                appearance.skinColorCode = FormatColor((byte)sNpc.Base["SkinColor"], GetSkinColorMapIndex(tribe.Key, isMale));

                // Bust & Muscles - flex fields.
                if (race.Key == 5 || race.Key == 1)
                {
                    // Roegadyn & Hyur
                    appearance.muscle = (byte)sNpc.Base["BustOrTone1"];
                    if (!isMale)
                    {
                        appearance.bust = (byte)sNpc.Base["ExtraFeature2OrBust"];
                    }
                }
                else if (race.Key == 6 && isMale)
                {
                    // Au Ra male muscles
                    appearance.muscle = (byte)sNpc.Base["BustOrTone1"];
                }
                else if (!isMale)
                {
                    // Other female bust sizes
                    appearance.bust = (byte)sNpc.Base["BustOrTone1"];
                }

                // Hair & Highlights
                var hairstyle     = (byte)sNpc.Base["HairStyle"];
                var hairstyleIcon = CustomizeIcon(GetHairstyleCustomizeIndex(tribe.Key, isMale), 100, hairstyle, npc);
                if (hairstyleIcon > 0)
                {
                    appearance.hairStyle = hairstyleIcon;
                }

                appearance.hairColor     = FormatColorCoordinates((byte)sNpc.Base["HairColor"]);
                appearance.hairColorCode = FormatColor((byte)sNpc.Base["HairColor"], GetHairColorMapIndex(tribe.Key, isMale));

                var highlights = Unpack2((byte)sNpc.Base["HairHighlight"]);
                if (highlights.Item1 == 1)
                {
                    appearance.highlightColor     = FormatColorCoordinates((byte)sNpc.Base["HairHighlightColor"]);
                    appearance.highlightColorCode = FormatColor((byte)sNpc.Base["HairHighlightColor"], HairHighlightColorOffset);
                }

                // Eyes & Heterochromia
                var eyeShape = Unpack2((byte)sNpc.Base["EyeShape"]);
                appearance.eyeSize  = eyeShape.Item1 == 1 ? "Small" : "Large";
                appearance.eyeShape = 1 + eyeShape.Item2;

                var eyeColor = (byte)sNpc.Base["EyeColor"];
                appearance.eyeColor     = FormatColorCoordinates(eyeColor);
                appearance.eyeColorCode = FormatColor(eyeColor, EyeColorOffset);

                var heterochromia = (byte)sNpc.Base["EyeHeterochromia"];
                if (heterochromia != eyeColor)
                {
                    appearance.heterochromia     = FormatColorCoordinates(heterochromia);
                    appearance.heterochromiaCode = FormatColor(heterochromia, EyeColorOffset);
                }

                // Mouth & Lips
                var mouth = Unpack2((byte)sNpc.Base["Mouth"]);
                appearance.mouth = 1 + mouth.Item2;

                if (mouth.Item1 == 1)
                {
                    var lipColor = Unpack2((byte)sNpc.Base["LipColor"]);
                    appearance.lipShade     = lipColor.Item1 == 1 ? "Light" : "Dark";
                    appearance.lipColor     = FormatColorCoordinates(lipColor.Item2);
                    appearance.lipColorCode = FormatColor(lipColor.Item2, lipColor.Item1 == 1 ? LightLipFacePaintColorOffset : DarkLipFacePaintColorOffset);
                }

                // Extra Features
                var extraFeatureName = ExtraFeatureName(race.Key);
                if (extraFeatureName != null)
                {
                    appearance.extraFeatureName = extraFeatureName;

                    appearance.extraFeatureShape = (byte)sNpc.Base["ExtraFeature1"];
                    appearance.extraFeatureSize  = (byte)sNpc.Base["ExtraFeature2OrBust"];
                }

                // Facepaint
                var facepaint     = Unpack2((byte)sNpc.Base["FacePaint"]);
                var facepaintIcon = CustomizeIcon(GetFacePaintCustomizeIndex(tribe.Key, isMale), 50, facepaint.Item2, npc);
                if (facepaintIcon > 0)
                {
                    appearance.facepaint = facepaintIcon;

                    if (facepaint.Item1 == 1)
                    {
                        appearance.facepaintReverse = 1;
                    }

                    var facepaintColor = Unpack2((byte)sNpc.Base["FacePaintColor"]);
                    appearance.facepaintShade     = facepaintColor.Item1 == 1 ? "Light" : "Dark";
                    appearance.facepaintColor     = FormatColorCoordinates(facepaintColor.Item2);
                    appearance.facepaintColorCode = FormatColor(facepaintColor.Item2, facepaintColor.Item1 == 1 ? LightLipFacePaintColorOffset : DarkLipFacePaintColorOffset);
                }

                // Facial Features
                var facialfeature = (byte)sNpc.Base["FacialFeature"];
                if (facialfeature != 0 && isValidFace)
                {
                    var type = CharaMakeTypeRow(tribe.Key, gender);

                    appearance.facialfeatures = new JArray();

                    // There are only 7 groups of facial features at the moment.
                    var facialfeatures = new System.Collections.BitArray(new byte[] { facialfeature });
                    for (var i = 0; i < 7; i++)
                    {
                        if (!facialfeatures[i])
                        {
                            continue;
                        }

                        // Columns are split into groups of 6, 1 for each face type.
                        var iconIndex = (i * 6) + face - 1;
                        var icon      = (ImageFile)type["FacialFeatureIcon[" + iconIndex + "]"];
                        appearance.facialfeatures.Add(IconDatabase.EnsureEntry("customize", icon));
                    }

                    appearance.facialfeatureColor     = FormatColorCoordinates((byte)sNpc.Base["FacialFeatureColor"]);
                    appearance.facialfeatureColorCode = FormatColor((byte)sNpc.Base["FacialFeatureColor"], 0);
                }

                // todo: CharaMakeType ExtraFeatureData for faces, extra feature icons.
            }
        }
Example #20
0
        void BuildTraits()
        {
            dynamic traitCategory = new JObject();

            traitCategory.id   = -1;
            traitCategory.name = "Trait";
            _builder.Db.ActionCategories.Add(traitCategory);

            foreach (var sTrait in _builder.Sheet <Saint.Trait>())
            {
                if (sTrait.ClassJob.Key == 0)
                {
                    continue; // Skip adventurer traits atm.
                }
                var sTraitTransient = _builder.Sheet("TraitTransient")[sTrait.Key];

                dynamic trait = new JObject();
                trait.id = sTrait.Key + 50000; // Arbitrary!
                _builder.Localize.Strings((JObject)trait, sTrait, "Name");
                _builder.Localize.HtmlStrings((JObject)trait, sTraitTransient, "Description");
                trait.category = (int)traitCategory.id;
                trait.icon     = IconDatabase.EnsureEntry("action", sTrait.Icon);
                trait.job      = sTrait.ClassJob.Key;
                trait.affinity = sTrait.ClassJobCategory.Key;
                trait.lvl      = sTrait.Level;

                string desc = trait.en.description;

                // Link traits if the action name appears somewhere in the trait description.
                foreach (var action in _linkingActions)
                {
                    if (action.job == null)
                    {
                        continue;
                    }

                    string name = action.en.name;
                    if (name == null || !desc.Contains(name))
                    {
                        continue;
                    }

                    if ((int)action.job != (int)trait.job)
                    {
                        continue;
                    }

                    var actionId = (int)action.id;
                    var traitId  = (int)trait.id;

                    if (actionId == 119 && traitId == 50065)
                    {
                        continue; // Graniteskin & Stone
                    }
                    if (actionId == 120 && traitId == 50063)
                    {
                        continue; // Overcure and Cure
                    }
                    if (trait.actions == null)
                    {
                        trait.actions = new JArray();
                    }
                    trait.actions.Add(actionId);
                    _builder.Db.AddReference(trait, "action", actionId, false);

                    if (action.traits == null)
                    {
                        action.traits = new JArray();
                    }
                    action.traits.Add(traitId);
                    _builder.Db.AddReference(action, "action", traitId, false);
                }

                _builder.Db.Actions.Add(trait);
            }
        }
Example #21
0
        void BuildCards()
        {
            foreach (var sItem in _builder.ItemsToImport)
            {
                var unlock = sItem.ItemAction as SaintCoinach.Xiv.ItemActions.TripleTriadCardUnlock;
                if (unlock == null)
                {
                    continue;
                }

                var item = _builder.Db.ItemsById[sItem.Key];
                if (item.tripletriad != null)
                {
                    throw new InvalidOperationException();
                }

                var sCard     = unlock.TripleTriadCard;
                var sResident = unlock.TripleTriadCard.TripleTriadCardResident;

                item.tripletriad = new JObject();
                _builder.Localize.Strings(item.tripletriad, unlock.TripleTriadCard, "Description");

                var type = sResident.TripleTriadCardType.Name.ToString();
                if (!string.IsNullOrEmpty(type))
                {
                    item.tripletriad.type = type;
                }

                if (sResident.SaleValue > 0)
                {
                    item.tripletriad.sellMgp = sResident.SaleValue;
                }

                // unlock.TripleTriadCard.Icon is only 40x40 and looks awful.
                item.tripletriad.plate = IconDatabase.EnsureEntry("triad\\plate", sCard.PlateIcon);

                item.tripletriad.rarity = sResident.TripleTriadCardRarity.Key;

                if (sResident.Top == 10)
                {
                    item.tripletriad.top = "A";
                }
                else
                {
                    item.tripletriad.top = sResident.Top;
                }

                if (sResident.Bottom == 10)
                {
                    item.tripletriad.bottom = "A";
                }
                else
                {
                    item.tripletriad.bottom = sResident.Bottom;
                }

                if (sResident.Left == 10)
                {
                    item.tripletriad.left = "A";
                }
                else
                {
                    item.tripletriad.left = sResident.Left;
                }

                if (sResident.Right == 10)
                {
                    item.tripletriad.right = "A";
                }
                else
                {
                    item.tripletriad.right = sResident.Right;
                }

                _itemsByTripleTriadCardId[sCard.Key] = item;
            }
        }
Example #22
0
        void BuildQuests()
        {
            var lQuestsByKey = _builder.Libra.Table <Libra.Quest>().ToDictionary(q => q.Key);

            foreach (var sQuest in _builder.Sheet <Saint.Quest>())
            {
                if (sQuest.Key == 65536 || sQuest.Name == "")
                {
                    continue; // Test quests
                }
                dynamic quest = new JObject();
                quest.id = sQuest.Key;
                _builder.Localize.Strings((JObject)quest, sQuest, Utils.SanitizeQuestName, "Name");
                quest.patch = PatchDatabase.Get("quest", sQuest.Key);
                quest.sort  = sQuest.SortKey;

                // Quest location
                var questIssuer = (sQuest.IssuingENpc?.Locations?.Count() ?? 0) > 0 ? sQuest.IssuingENpc : null;
                var sPlaceName  = sQuest.PlaceName;
                if (sPlaceName.Name == "" && questIssuer != null)
                {
                    sPlaceName = questIssuer.Locations.First().PlaceName;
                }

                _builder.Localize.Column((JObject)quest, sPlaceName, "Name", "location",
                                         x => x == "" ? "???" : x.ToString());

                // Repeatability
                if (sQuest.RepeatInterval == Saint.QuestRepeatInterval.Daily)
                {
                    quest.interval = "daily";
                }
                else if (sQuest.RepeatInterval == Saint.QuestRepeatInterval.Weekly)
                {
                    quest.interval = "weekly";
                }

                if (sQuest.IsRepeatable)
                {
                    quest.repeatable = 1;
                }

                // Miscellaneous
                if (sQuest.Icon != null)
                {
                    quest.icon = IconDatabase.EnsureEntry("quest", sQuest.Icon);
                }

                if (sQuest.BeastTribe.Key != 0)
                {
                    quest.beast = sQuest.BeastTribe.Key;
                }

                ImportQuestEventIcon(quest, sQuest);

                // Quest issuer
                if (questIssuer != null)
                {
                    var npc = AddQuestNpc(quest, questIssuer);
                    if (npc != null)
                    {
                        quest.issuer = questIssuer.Key;
                    }
                }

                // Quest target
                var questTarget = sQuest.TargetENpc;
                if (questTarget != null)
                {
                    var npc = AddQuestNpc(quest, questTarget);
                    if (npc != null)
                    {
                        quest.target = questTarget.Key;
                    }
                }

                // Involved
                if (_npcsByQuestKey.TryGetValue(sQuest.Key, out var involvedNpcKeys))
                {
                    foreach (var npcKey in involvedNpcKeys)
                    {
                        var sInvolvedEnpc = _builder.Realm.GameData.ENpcs[npcKey];
                        if (sInvolvedEnpc == null || sInvolvedEnpc == questTarget || sInvolvedEnpc == questIssuer)
                        {
                            continue;
                        }

                        var npc = AddQuestNpc(quest, sInvolvedEnpc);
                        if (npc == null)
                        {
                            continue;
                        }

                        if (quest.involved == null)
                        {
                            quest.involved = new JArray();
                        }
                        quest.involved.Add(npcKey);
                    }
                }

                // Journal Genre
                quest.genre = sQuest.JournalGenre.Key;

                // Rewards
                dynamic rewards = new JObject();
                if (sQuest.Rewards.Gil > 0)
                {
                    rewards.gil = sQuest.Rewards.Gil;
                }

                if (sQuest.Rewards.Emote.Key > 0)
                {
                    rewards.emote = sQuest.Rewards.Emote.Name.ToString();
                }

                if (sQuest.Rewards.ClassJob.Key > 0)
                {
                    rewards.job = sQuest.Rewards.ClassJob.Key;
                }

                if (sQuest.AsInt32("CurrencyRewardCount") > 0)
                {
                    rewards.gcseal = sQuest.AsInt32("CurrencyRewardCount");
                }

                if (sQuest.Rewards.Action.Key > 0)
                {
                    rewards.action = sQuest.Rewards.Action.Key;

                    _builder.Db.AddReference(quest, "action", sQuest.Rewards.Action.Key, false);
                }

                var sInstanceContentReward = sQuest.Rewards.InstanceContent;
                if (sInstanceContentReward.Key > 0)
                {
                    var instance = _builder.Db.Instances.FirstOrDefault(i => i.id == sInstanceContentReward.Key);
                    if (instance != null)
                    {
                        instance.unlockedByQuest = sQuest.Key;
                        rewards.instance         = sInstanceContentReward.Key;

                        _builder.Db.AddReference(quest, "instance", sInstanceContentReward.Key, false);
                        _builder.Db.AddReference(instance, "quest", sQuest.Key, false);
                    }
                }

                if (sQuest.Rewards.Reputation > 0)
                {
                    rewards.reputation = sQuest.Rewards.Reputation;
                }

                if (sQuest.Rewards.QuestRewardOther.Name == "Aether Current")
                {
                    rewards.aetherCurrent = 1;
                }

                foreach (var sQuestRewardItemGroup in sQuest.Rewards.Items)
                {
                    foreach (var sQuestRewardItem in sQuestRewardItemGroup.Items)
                    {
                        if (rewards.items == null)
                        {
                            rewards.items = new JArray();
                        }

                        var maxCount = sQuestRewardItem.Counts.Max();

                        dynamic o = new JObject();
                        if (maxCount > 1)
                        {
                            o.num = maxCount;
                        }
                        o.id = sQuestRewardItem.Item.Key;

                        if (sQuestRewardItemGroup.Type == Saint.QuestRewardGroupType.One)
                        {
                            o.one = 1;
                        }

                        if (sQuestRewardItem.IsHq)
                        {
                            o.hq = 1;
                        }

                        rewards.items.Add(o);

                        try
                        {
                            var item = _builder.Db.ItemsById[sQuestRewardItem.Item.Key];
                            if (item.quests == null)
                            {
                                item.quests = new JArray();
                            }
                            JArray quests = item.quests;

                            if (!quests.Any(id => ((int)id) == sQuest.Key))
                            {
                                quests.Add(sQuest.Key);
                            }
                            _builder.Db.AddReference(item, "quest", sQuest.Key, false);
                        }
                        catch (KeyNotFoundException ignored)
                        {
                            DatabaseBuilder.PrintLine($"Reward item '{sQuestRewardItem.Item.Key}' not found for Quest '{quest.Key}'.");
                        }

                        _builder.Db.AddReference(quest, "item", sQuestRewardItem.Item.Key, false);
                    }
                }

                // Libra supplemental rewards.
                if (lQuestsByKey.TryGetValue(sQuest.Key, out var lQuest))
                {
                    dynamic data = JsonConvert.DeserializeObject(lQuest.data);
                    int     xp   = 0;
                    if (data.exp != null && int.TryParse((string)data.exp, out xp))
                    {
                        rewards.xp = xp;
                    }
                }

                // Scripts
                var instructions = ScriptInstruction.Read(sQuest, 50);

                // Script instance unlocks.
                if (!sQuest.IsRepeatable)
                {
                    var instanceReferences = instructions.Where(i => i.Label.StartsWith("INSTANCEDUNGEON")).ToArray();
                    foreach (var instanceReference in instanceReferences)
                    {
                        var key      = (int)instanceReference.Argument;
                        var instance = _builder.Db.Instances.FirstOrDefault(i => ((int)i.id) == key);
                        if (instance == null)
                        {
                            continue; // Probably a guildhest.
                        }
                        if (instance.unlockedByQuest != null)
                        {
                            continue;
                        }

                        if (!instructions.Any(i => i.Label == "UNLOCK_ADD_NEW_CONTENT_TO_CF" || i.Label.StartsWith("UNLOCK_DUNGEON")))
                        {
                            // Some quests reference instances for the retrieval of items.
                            // Don't treat these as unlocks.
                            if (instructions.Any(i => i.Label.StartsWith("LOC_ITEM")))
                            {
                                continue;
                            }
                        }

                        instance.unlockedByQuest = sQuest.Key;
                        rewards.instance         = key;

                        _builder.Db.AddReference(quest, "instance", key, false);
                        _builder.Db.AddReference(instance, "quest", sQuest.Key, false);
                    }
                }

                // Used items.
                foreach (var instruction in instructions)
                {
                    if (!instruction.Label.StartsWith("RITEM") && !instruction.Label.StartsWith("QUEST_ITEM"))
                    {
                        continue;
                    }

                    var key = (int)instruction.Argument;
                    if (_builder.Db.ItemsById.TryGetValue(key, out var item))
                    {
                        if (item.usedInQuest == null)
                        {
                            item.usedInQuest = new JArray();
                        }

                        JArray usedInQuest = item.usedInQuest;
                        if (usedInQuest.Any(i => (int)i == sQuest.Key))
                        {
                            continue;
                        }

                        item.usedInQuest.Add(sQuest.Key);

                        if (quest.usedItems == null)
                        {
                            quest.usedItems = new JArray();
                        }
                        quest.usedItems.Add(key);

                        _builder.Db.AddReference(item, "quest", sQuest.Key, false);
                        _builder.Db.AddReference(quest, "item", key, false);
                    }
                }

                ImportQuestLore(quest, sQuest, instructions);

                if (((JObject)rewards).Count > 0)
                {
                    quest.reward = rewards;
                }

                ImportQuestRequirements(quest, sQuest);

                _builder.Db.Quests.Add(quest);
                _builder.Db.QuestsById[sQuest.Key] = quest;
            }
        }
Example #23
0
        dynamic BuildAction(Saint.Action sAction)
        {
            dynamic action = new JObject();

            action.id = sAction.Key;
            _builder.Localize.Strings((JObject)action, sAction, "Name");
            _builder.Localize.HtmlStrings((JObject)action, sAction.ActionTransient, "Description");
            action.patch    = PatchDatabase.Get("action", sAction.Key);
            action.category = sAction.ActionCategory.Key;

            if (!sAction.Icon.Path.EndsWith("000000.tex"))
            {
                action.icon = IconDatabase.EnsureEntry("action", sAction.Icon);
            }

            if (sAction.ClassJobCategory.Key > 0)
            {
                action.affinity = sAction.ClassJobCategory.Key;
            }

            action.lvl    = sAction.ClassJobLevel;
            action.range  = sAction.Range; // -1 = melee, 0 = self
            action.cast   = sAction.CastTime.TotalMilliseconds;
            action.recast = sAction.RecastTime.TotalMilliseconds;

            // This is needed for ninja and pet actions.
            var sClassJob = sAction.ClassJob;

            if (sClassJob == null || (sClassJob.Key == 0 && sAction.ClassJobCategory.ClassJobs.Count() == 1))
            {
                if (sAction.ClassJobCategory.Key > 0)
                {
                    action.job = sAction.ClassJobCategory.ClassJobs.First().Key;
                }
            }
            else
            {
                action.job = sClassJob.Key;
            }

            if (sAction.ComboFrom.Key > 0)
            {
                action.comboFrom = sAction.ComboFrom.Key;

                _builder.Db.AddReference(action, "action", sAction.ComboFrom.Key, false);
            }

            if (sAction.GainedStatus.Key != 0)
            {
                action.gainedStatus = GetStatus(sAction.GainedStatus);
            }

            if (sAction.EffectRange > 1)
            {
                action.size = sAction.EffectRange;
            }

            BuildActionCost(action, sAction);

            var cooldownGroup = (byte)sAction["CooldownGroup"];

            if (cooldownGroup == 58)
            {
                action.gcd = 1;
            }

            // Not very useful.
            //if (gameData.CanTargetSelf)
            //    action.self = 1;
            //if (gameData.CanTargetParty)
            //    action.party = 1;
            //if (gameData.CanTargetFriendly)
            //    action.friendly = 1;
            //if (gameData.CanTargetHostile)
            //    action.hostile = 1;
            //if (gameData.TargetArea)
            //    action.aoe = 1;

            _builder.Db.Actions.Add(action);
            _builder.Db.ActionsById[sAction.Key] = action;

            return(action);
        }
Example #24
0
        void BuildLeve(Game.Leve sLeve)
        {
            if (sLeve.Key <= 20 || sLeve.Name == "")
            {
                return;
            }

            dynamic leve = new JObject();

            leve.id = sLeve.Key;
            _builder.Localize.HtmlStrings((JObject)leve, sLeve, "Name", "Description");
            leve.patch       = PatchDatabase.Get("leve", sLeve.Key);
            leve.client      = sLeve.LeveClient.Name.ToString().Replace("Client: ", "");
            leve.lvl         = sLeve.ClassJobLevel;
            leve.jobCategory = sLeve.ClassJobCategory.Key;

            var sNpc     = _builder.Realm.GameData.ENpcs[sLeve.LevemeteLevel.Object.Key];
            var levemete = _builder.Db.NpcsById[sNpc.Key];

            leve.levemete = sNpc.Key;
            _builder.Db.AddReference(leve, "npc", sNpc.Key, false);

            if (sLeve.StartLevel != null && sLeve.StartLevel.Key != 0)
            {
                leve.coords = _builder.GetCoords(sLeve.StartLevel);

                var locationInfo = _builder.LocationInfoByMapId[sLeve.StartLevel.Map.Key];
                leve.zoneid = locationInfo.PlaceName.Key;
            }

            leve.areaid = sLeve.PlaceNameStart.Key;
            _builder.Db.AddLocationReference(sLeve.PlaceNameStart.Key);

            if (sLeve.ExpReward > 0)
            {
                leve.xp = sLeve.ExpReward;
            }
            else if (sLeve.ClassJobCategory.Name.Equals("MIN") ||
                     sLeve.ClassJobCategory.Name.Equals("BTN") ||
                     sLeve.ClassJobCategory.Name.Equals("FSH"))
            {
                if ((float)sLeve["ExpFactor"] > 0)
                {
                    leve.xp = ((float)sLeve["ExpFactor"]) * _gatherExpByLvl[leve.lvl.Value].Exp;
                }
            }

            if (sLeve.GilReward > 0)
            {
                leve.gil = sLeve.GilReward;
            }

            switch (sLeve.LeveAssignmentType.Key)
            {
            case 16:     // Maelstrom
            case 17:     // Adders
            case 18:     // Flames
                leve.gc = sLeve.LeveAssignmentType.Key - 15;
                break;
            }

            if (sLeve.LeveRewardItem.ItemGroups.Any(ig => ig.Value.Key > 0))
            {
                // Embed the rewards, as they will be kept in separate files.
                leve.rewards = sLeve.LeveRewardItem.Key;

                foreach (var group in sLeve.LeveRewardItem.ItemGroups.SelectMany(g => g.Value.Items))
                {
                    var item = _builder.Db.ItemsById[group.Item.Key];
                    if (item.category == 59) // Crystal
                    {
                        continue;            // Skip these, there are too many.
                    }
                    if (item.leves == null)
                    {
                        item.leves = new JArray();
                    }

                    JArray leves = item.leves;
                    if (!leves.Any(l => (int)l == sLeve.Key))
                    {
                        leves.Add(sLeve.Key);
                        _builder.Db.AddReference(item, "leve", sLeve.Key, false);
                        _builder.Db.AddReference(leve, "item", group.Item.Key, false);
                    }
                }
            }

            leve.plate    = IconDatabase.EnsureEntry("leve\\plate", sLeve.PlateIcon);
            leve.frame    = IconDatabase.EnsureEntry("leve\\frame", sLeve.FrameIcon);
            leve.areaicon = IconDatabase.EnsureEntry("leve\\area", sLeve.IssuerIcon);

            // Find turn-ins for crafting and fisher leves.
            if (_craftLevesByLeve.TryGetValue(sLeve, out var sCraftLeve))
            {
                if (sCraftLeve.Repeats > 0)
                {
                    leve.repeats = sCraftLeve.Repeats;
                }

                JArray requires = new JArray();
                leve.requires = requires;
                foreach (var sCraftLeveItem in sCraftLeve.Items)
                {
                    dynamic entry = requires.FirstOrDefault(t => (int)t["item"] == sCraftLeveItem.Item.Key);
                    if (entry != null)
                    {
                        if (entry.amount == null)
                        {
                            entry.amount = 1;
                        }
                        entry.amount += sCraftLeveItem.Count;
                        continue;
                    }

                    dynamic requireItem = new JObject();
                    requireItem.item = sCraftLeveItem.Item.Key;
                    if (sCraftLeveItem.Count > 1)
                    {
                        requireItem.amount = sCraftLeveItem.Count;
                    }
                    leve.requires.Add(requireItem);

                    var item = _builder.Db.ItemsById[sCraftLeveItem.Item.Key];
                    if (item.requiredByLeves == null)
                    {
                        item.requiredByLeves = new JArray();
                    }
                    item.requiredByLeves.Add(sLeve.Key);

                    _builder.Db.AddReference(item, "leve", sLeve.Key, false);
                    _builder.Db.AddReference(leve, "item", sCraftLeveItem.Item.Key, false);
                }
            }

            // TODO: CompanyLeve sheet for seal rewards and stuff?

            _builder.Db.Leves.Add(leve);
        }