Exemple #1
0
        public void Edit <T>(IAssetData asset)
        {
            /*********
            *  Local data
            *********/

            if (asset.AssetNameEquals(GameContentEventDataPath))
            {
                var events = ((Newtonsoft.Json.Linq.JArray)asset
                              .AsDictionary <string, object>()
                              .Data["Events"])
                             .ToObject <List <Dictionary <string, string> > >();

                // Events are populated with preset tokens and script dialogues depending on game locale.

                // Root event tokenisation
                for (int i = 0; i < events.Count; ++i)
                {
                    // Format event script with event NPC name, as well as their dialogue strings
                    string[] args = new string[] { events[i]["Who"] }
                    .Concat(new int[] { 1, 2, 3, 4 }
                            .Select(j => Translations.GetTranslation($"event.{i}.dialogue.{j}")))
                    .ToArray();
                    events[i]["Script"] = string.Format(
                        format: events[i]["Script"],
                        args: args);
                    events[i]["Conditions"] = string.Format(
                        format: events[i]["Conditions"],
                        events[i]["Who"]);
                }

                Log.T($"Loaded {events.Count} event(s).{Environment.NewLine}Root event: {events[0]["Where"]}/{events[0]["Conditions"]}");

                ModEntry.EventData = events;

                return;
            }

            /********
            *  Game data
            ********/

            int id = OutdoorPot.BaseParentSheetIndex;

            if (asset.AssetNameEquals(Path.Combine("Data", "BigCraftablesInformation")))
            {
                if (ModEntry.ItemDefinitions == null)
                {
                    return;
                }

                string[] fields;
                var      data = asset.AsDictionary <int, string>().Data;

                // Set or reset the item ID for the generic object to some first best available index
                id = OutdoorPot.BaseParentSheetIndex = data.Keys.Max() + 2;

                string name, description;

                // Patch generic object entry into bigcraftables file, including display name and description from localisations file
                name        = Translations.GetTranslation("item.name");
                description = Translations.GetTranslation("item.description.default");
                fields      = data.First().Value.Split('/');            // Use existing data as a template; most fields are common or unused
                fields[0]   = OutdoorPot.GenericName;
                fields[4]   = description;
                fields[8]   = name;
                data[id]    = string.Join("/", fields);

                // Patch in dummy object entries after generic object entry
                for (int i = 1; i < ModEntry.ItemDefinitions.Count; ++i)
                {
                    ItemDefinition d = ModEntry.ItemDefinitions[ModEntry.ItemDefinitions.Keys.ElementAt(i)];
                    name         = Translations.GetNameTranslation(data: d);
                    fields       = data[id].Split('/');
                    fields[4]    = description;
                    fields[8]    = name;
                    data[id + i] = string.Join("/", fields);
                }

                // Don't remove the generic craftable from data lookup, since it's used later for crafting recipes and defaults

                return;
            }
            if (asset.AssetNameEquals(Path.Combine("Data", "CraftingRecipes")))
            {
                if (ModEntry.ItemDefinitions == null || id < 0)
                {
                    return;
                }

                // As above for the craftables dictionary, the recipes dictionary needs to have
                // our varieties patched in to have them appear.
                // Since all objects share a single ParentSheetIndex, each crafting recipe will normally
                // only produce a generic/wooden object.
                // This is handled in HarmonyPatches.CraftingPage_ClickCraftingRecipe_Prefix, which
                // is also needed to produce an OutdoorPot instance rather than a StardewValley.Object.

                // Add crafting recipes for all object variants
                var data = asset.AsDictionary <string, string>().Data;
                foreach (KeyValuePair <string, ItemDefinition> idAndFields in ModEntry.ItemDefinitions)
                {
                    string[] newFields = new string[]
                    {                           // Crafting ingredients:
                        ItemDefinition.ParseRecipeIngredients(data: idAndFields.Value),
                        // Unused field:
                        "blue berry",
                        // Crafted item ID and quantity:
                        $"{OutdoorPot.BaseParentSheetIndex} {idAndFields.Value.RecipeCraftedCount}",
                        // Recipe is bigCraftable:
                        "true",
                        // Recipe conditions (we ignore these):
                        "blue berry",
                        // Recipe display name:
                        Translations.GetNameTranslation(data: idAndFields.Value)
                    };
                    data[OutdoorPot.GetNameFromVariantKey(idAndFields.Key)] = string.Join("/", newFields);
                }

                return;
            }
            if (asset.AssetName.StartsWith(Path.Combine("Data", "Events")) &&
                Path.GetFileNameWithoutExtension(asset.AssetName) is string where)
            {
                // Patch our event data into whatever location happens to match the one specified.
                // Event tokenisation is handled in the Edit block for GameContentEventDataPath.

                if (ModEntry.EventData != null &&
                    ModEntry.EventData.FirstOrDefault(e => e["Where"] == where) is Dictionary <string, string> eventData)
                {
                    string key = $"{ModEntry.EventRootId}{ModEntry.EventData.IndexOf(eventData)}/{eventData["Conditions"]}";
                    asset.AsDictionary <string, string>().Data[key] = eventData["Script"];
                }

                return;
            }
        }
        /// <inheritdoc />
        public override void Edit <T>(IAssetData asset)
        {
            // throw on invalid type
            if (typeof(Texture2D).IsAssignableFrom(typeof(T)) || typeof(Map).IsAssignableFrom(typeof(T)))
            {
                this.Monitor.Log($"Can't apply data patch \"{this.Path}\" to {this.TargetAsset}: this file isn't a data file (found {(typeof(Texture2D).IsAssignableFrom(typeof(T)) ? "image" : typeof(T).Name)}).", LogLevel.Warn);
                return;
            }

            // handle dictionary types
            if (typeof(T).IsGenericType && typeof(T).GetGenericTypeDefinition() == typeof(Dictionary <,>))
            {
                // get dictionary's key/value types
                Type[] genericArgs = typeof(T).GetGenericArguments();
                if (genericArgs.Length != 2)
                {
                    throw new InvalidOperationException("Can't parse the asset's dictionary key/value types.");
                }
                Type keyType   = typeof(T).GetGenericArguments().FirstOrDefault();
                Type valueType = typeof(T).GetGenericArguments().LastOrDefault();
                if (keyType == null)
                {
                    throw new InvalidOperationException("Can't parse the asset's dictionary key type.");
                }
                if (valueType == null)
                {
                    throw new InvalidOperationException("Can't parse the asset's dictionary value type.");
                }

                // get underlying apply method
                MethodInfo method = this.GetType().GetMethod(nameof(this.ApplyDictionary), BindingFlags.Instance | BindingFlags.NonPublic);
                if (method == null)
                {
                    throw new InvalidOperationException($"Can't fetch the internal {nameof(this.ApplyDictionary)} method.");
                }

                // invoke method
                method
                .MakeGenericMethod(keyType, valueType)
                .Invoke(this, new object[] { asset });
            }

            // handle list types
            else if (typeof(T).IsGenericType && typeof(T).GetGenericTypeDefinition() == typeof(List <>))
            {
                // get list's value type
                Type keyType = typeof(T).GetGenericArguments().FirstOrDefault();
                if (keyType == null)
                {
                    throw new InvalidOperationException("Can't parse the asset's list value type.");
                }

                // get underlying apply method
                MethodInfo method = this.GetType().GetMethod(nameof(this.ApplyList), BindingFlags.Instance | BindingFlags.NonPublic);
                if (method == null)
                {
                    throw new InvalidOperationException($"Can't fetch the internal {nameof(this.ApplyList)} method.");
                }

                // invoke method
                method
                .MakeGenericMethod(keyType)
                .Invoke(this, new object[] { asset });
            }

            // unknown type
            else
            {
                throw new NotSupportedException($"Unknown data asset type {typeof(T).FullName}, expected dictionary or list.");
            }
        }
        /// <summary>SMAPI's Asset Editor tries to edit a specific asset.</summary>
        /// <typeparam name="T">The Type of asset</typeparam>
        /// <param name="asset">The asset in question</param>
        public void Edit <T>(IAssetData asset)
        {
            //If the asset is hairstyles
            if (asset.AssetNameEquals("Characters\\Farmer\\hairstyles"))
            {
                //Don't edit if they have no hair content packs
                if (PackHelper.NumberOfHairstlyesAdded == 56)
                {
                    return;
                }

                //Create a new texture and set it as the old one
                Texture2D oldTexture = asset.AsImage().Data;
                Texture2D newTexture = new Texture2D(Game1.graphics.GraphicsDevice, oldTexture.Width, Math.Max(oldTexture.Height, 4096));
                asset.ReplaceWith(newTexture);
                asset.AsImage().PatchImage(oldTexture);

                //Loop through each hair loaded and extend the image
                foreach (var hair in PackHelper.HairList)
                {
                    Entry.Monitor.Log($"Patching {hair.ModName}", LogLevel.Trace);

                    //Ints to run throught the current hairstyles.png to find each hairstyle within the png
                    int hairTextureX = 0;
                    int hairTextureY = 0;

                    for (int i = 0; i < hair.NumberOfHairstyles; i++)
                    {
                        if ((HairTextureHeight + 96) >= 4032 && !Entry.IsSpaceCoreInstalled)
                        {
                            Entry.Monitor.Log($"{hair.ModName} hairstyles cannot be added to the game, the texture is too big.", LogLevel.Error);
                            return;
                        }

                        //Patch the hair texture and change the hair texture height
                        if (Entry.IsSpaceCoreInstalled)
                        {
                            Entry.SpaceCorePatchExtendedTileSheet(asset.AsImage(), hair.Texture, new Rectangle(hairTextureX, hairTextureY, 16, 96), new Rectangle(HairTextureWidth, HairTextureHeight, 16, 96));
                        }
                        else
                        {
                            asset.AsImage().PatchImage(hair.Texture, new Rectangle(hairTextureX, hairTextureY, 16, 96), new Rectangle(HairTextureWidth, HairTextureHeight, 16, 96));
                        }

                        //Change which hair is being added from the source texture
                        if (hairTextureX + 16 == 128)
                        {
                            hairTextureX  = 0;
                            hairTextureY += 96;
                        }
                        else
                        {
                            hairTextureX += 16;
                        }

                        //Change where to put the hair on the asset
                        if (HairTextureWidth + 16 >= 128)
                        {
                            HairTextureWidth   = 0;
                            HairTextureHeight += 96;
                        }
                        else
                        {
                            HairTextureWidth += 16;
                        }
                    }
                }

                //Cut the blank image from the image
                if (!Entry.IsSpaceCoreInstalled || PackHelper.NumberOfHairstlyesAdded < 335)
                {
                    CutEmptyImage(asset, HairTextureHeight, 128);
                }
            }

            //If the asset is accessories
            if (asset.AssetNameEquals("Characters\\Farmer\\accessories"))
            {
                //Create a new texture and set it as the old one
                Texture2D oldTexture = asset.AsImage().Data;
                Texture2D newTexture = new Texture2D(Game1.graphics.GraphicsDevice, oldTexture.Width, Math.Max(oldTexture.Height, 4096));
                asset.ReplaceWith(newTexture);
                asset.AsImage().PatchImage(oldTexture);

                //Loop through each accessory loaded and extend the image
                foreach (var accessory in PackHelper.AccessoryList)
                {
                    //Ints to run throught the current hairstyles.png to find each hairstyle within the png
                    int accessoryTextureX = 0;
                    int accessoryTextureY = 0;

                    for (int i = 0; i < accessory.NumberOfAccessories; i++)
                    {
                        if ((AccessoryTextureHeight + 96) >= 4096)
                        {
                            Entry.Monitor.Log($"{accessory.ModName} accessories cannot be added to the game, the texture is too big.", LogLevel.Error);
                            return;
                        }

                        //Patch the hair texture and change the hair texture height
                        asset.AsImage().PatchImage(accessory.Texture, new Rectangle(accessoryTextureX, accessoryTextureY, 16, 32), new Rectangle(AccessoryTextureWidth, AccessoryTextureHeight, 16, 32));

                        //Change which accessory is being added from the source texture
                        if (accessoryTextureX + 16 == 128)
                        {
                            accessoryTextureX  = 0;
                            accessoryTextureY += 32;
                        }
                        else
                        {
                            accessoryTextureX += 16;
                        }

                        //Change where to put the accessory on the asset
                        if (AccessoryTextureWidth + 16 == 128)
                        {
                            AccessoryTextureWidth   = 0;
                            AccessoryTextureHeight += 32;
                        }
                        else
                        {
                            AccessoryTextureWidth += 16;
                        }
                    }
                }

                //Cut the blank image from the image
                //CutEmptyImage(asset, AccessoryTextureHeight, 128);
            }

            if (asset.AssetNameEquals("Characters\\Farmer\\skinColors"))
            {
                //Break if the skin colors were already edited
                if (SkinEditedCounter != 4)
                {
                    SkinEditedCounter++;
                    return;
                }

                SkinEditedCounter = 0;
                //Create a new texture and set it as the old one
                Texture2D oldTexture = asset.AsImage().Data;
                Texture2D newTexture = new Texture2D(Game1.graphics.GraphicsDevice, oldTexture.Width, Math.Max(oldTexture.Height, 4096));
                asset.ReplaceWith(newTexture);
                asset.AsImage().PatchImage(oldTexture);

                //Loop through each skin color loaded and extend the image
                foreach (var skinColor in PackHelper.SkinColorList)
                {
                    if ((skinColor.TextureHeight + SkinColorTextureHeight) > 4096)
                    {
                        Entry.Monitor.Log($"{skinColor.ModName} skin colors cannot be added to the game, the texture is too big. Please show this Alert to MartyrPher and I guess they'll have to extend the skinColor image...uhh yea. Why do you need 4096 skin colors anyway?", LogLevel.Alert);
                        return;
                    }

                    //Patch the skin color and change the skin color height
                    asset.AsImage().PatchImage(skinColor.Texture, null, new Rectangle(0, SkinColorTextureHeight, 3, skinColor.TextureHeight));
                    SkinColorTextureHeight += skinColor.TextureHeight;
                }

                //Cut the blank image
                CutEmptyImage(asset, SkinColorTextureHeight, 3);
            }

            //If the asset is the dresser
            if (asset.AssetNameEquals($"Mods/{Entry.ModManifest.UniqueID}/dresser.png"))
            {
                //Break if the image was already edited
                if (WasDresserImageEdited)
                {
                    return;
                }

                //Set dresser was edited and replace the old tecture with a new one
                WasDresserImageEdited = true;
                Texture2D oldTexture = asset.AsImage().Data;
                Texture2D newTexture = new Texture2D(Game1.graphics.GraphicsDevice, 16, Math.Max(oldTexture.Height, 4096));
                asset.ReplaceWith(newTexture);
                asset.AsImage().PatchImage(oldTexture);

                //Loop through each dresser and patch the image
                foreach (var dresser in PackHelper.DresserList)
                {
                    if ((dresser.TextureHeight + DresserTextureHeight) > 4096)
                    {
                        Entry.Monitor.Log($"{dresser.ModName} dressers cannot be added to the game, the texture is too big.", LogLevel.Warn);
                        return;
                    }

                    //Patch the dresser.png and adjust the height
                    asset.AsImage().PatchImage(dresser.Texture, null, new Rectangle(0, DresserTextureHeight, 16, dresser.TextureHeight));
                    DresserTextureHeight += dresser.TextureHeight;
                }

                Entry.Monitor.Log($"Dresser Image height is now: {DresserTextureHeight}", LogLevel.Trace);

                //Cut the empty image from the dresser texture
                CutEmptyImage(asset, DresserTextureHeight, 16);

                //Save the dresser to the mod folder so it can be used to create a TileSheet for the Farmhouse
                FileStream stream = new FileStream(Path.Combine(Entry.Helper.DirectoryPath, "assets", "dresser.png"), FileMode.Create);
                asset.AsImage().Data.SaveAsPng(stream, 16, DresserTextureHeight);
                stream.Close();
            }
        }
        /// <summary>Edit the farm animals asset to add the the custom data strings.</summary>
        /// <typeparam name="T">The type of the assets being loaded.</typeparam>
        /// <param name="asset">The asset data being loaded.</param>
        public void Edit <T>(IAssetData asset)
        {
            var data = asset.AsDictionary <string, string>().Data;

            data = DataStrings;
        }
Exemple #5
0
        public void Edit <T>(IAssetData asset)
        {
            ModEntry.RecipesAdded.Clear();
            IDictionary <string, string> BaseData = asset.AsDictionary <string, string>().Data;

            KnownRecipes = GetRecipesFromAsset(asset);
            int AddedRecipes = 0;

            Dictionary <int, int> AllIngredients = new Dictionary <int, int>();

            foreach (CookingRecipe testRecipe in KnownRecipes)
            {
                AllIngredients.Clear();

                AllIngredients = GetAllIngredientsFromChildren(testRecipe.OutputID);

                bool isNew = !testRecipe.Ingredients.ContentEquals(AllIngredients);

                if (isNew)
                {
                    CookingRecipe newRecipe = new CookingRecipe()
                    {
                        Name        = testRecipe.Name,
                        Source      = "null",// testRecipe.Source,
                        MysteryText = testRecipe.MysteryText,
                        OutputID    = testRecipe.OutputID,
                        Ingredients = AllIngredients
                    };

                    string NameToAdd = newRecipe.GetKey();

                    if (ModEntry.RecipesAdded.TryGetValue(newRecipe.GetKey(), out List <string> SecondaryRecipes))
                    {
                        //We have already created a list
                        if (SecondaryRecipes.Count == 0)
                        {
                            NameToAdd = $"Alt: {NameToAdd}";
                        }
                        else
                        {
                            NameToAdd = $"Alt {SecondaryRecipes.Count + 1}: {NameToAdd}";
                        }

                        SecondaryRecipes.Add(NameToAdd);
                        Logger.LogDebug($"{newRecipe.GetKey()}: {string.Join(", ", SecondaryRecipes)}");
                    }
                    else
                    {
                        NameToAdd = $"Alt: {NameToAdd}";
                        ModEntry.RecipesAdded.Add(newRecipe.GetKey(), new List <string>()
                        {
                            $"{NameToAdd}"
                        });
                    }

                    BaseData[NameToAdd] = newRecipe.GetValue();
                    AddedRecipes++;
                }
            }
            Logger.LogInfo($"Added {AddedRecipes} recipes!");
        }
Exemple #6
0
        /// <summary>Apply any <see cref="Editors"/> to a loaded asset.</summary>
        /// <typeparam name="T">The asset type.</typeparam>
        /// <param name="info">The basic asset metadata.</param>
        /// <param name="asset">The loaded asset.</param>
        private IAssetData ApplyEditors <T>(IAssetInfo info, IAssetData asset)
        {
            IAssetData GetNewData(object data) => new AssetDataForObject(info, data, this.AssertAndNormalizeAssetName);

            // special case: if the asset was loaded with a more general type like 'object', call editors with the actual type instead.
            {
                Type actualType     = asset.Data.GetType();
                Type actualOpenType = actualType.IsGenericType ? actualType.GetGenericTypeDefinition() : null;

                if (typeof(T) != actualType && (actualOpenType == typeof(Dictionary <,>) || actualOpenType == typeof(List <>) || actualType == typeof(Texture2D)))
                {
                    return((IAssetData)this.GetType()
                           .GetMethod(nameof(this.ApplyEditors), BindingFlags.NonPublic | BindingFlags.Instance)
                           .MakeGenericMethod(actualType)
                           .Invoke(this, new object[] { info, asset }));
                }
            }

            // edit asset
            foreach (var entry in this.Editors)
            {
                // check for match
                IModMetadata mod    = entry.Mod;
                IAssetEditor editor = entry.Data;
                try
                {
                    if (!editor.CanEdit <T>(info))
                    {
                        continue;
                    }
                }
                catch (Exception ex)
                {
                    mod.LogAsMod($"Mod crashed when checking whether it could edit asset '{info.AssetName}', and will be ignored. Error details:\n{ex.GetLogSummary()}", LogLevel.Error);
                    continue;
                }

                // try edit
                object prevAsset = asset.Data;
                try
                {
                    editor.Edit <T>(asset);
                    this.Monitor.Log($"{mod.DisplayName} edited {info.AssetName}.");
                }
                catch (Exception ex)
                {
                    mod.LogAsMod($"Mod crashed when editing asset '{info.AssetName}', which may cause errors in-game. Error details:\n{ex.GetLogSummary()}", LogLevel.Error);
                }

                // validate edit
                if (asset.Data == null)
                {
                    mod.LogAsMod($"Mod incorrectly set asset '{info.AssetName}' to a null value; ignoring override.", LogLevel.Warn);
                    asset = GetNewData(prevAsset);
                }
                else if (!(asset.Data is T))
                {
                    mod.LogAsMod($"Mod incorrectly set asset '{asset.AssetName}' to incompatible type '{asset.Data.GetType()}', expected '{typeof(T)}'; ignoring override.", LogLevel.Warn);
                    asset = GetNewData(prevAsset);
                }
            }

            // return result
            return(asset);
        }
Exemple #7
0
        public void Edit <T>(IAssetData asset)
        {
            VoidshroomSpore.setIndex(); //get an item index for voidshroom spores if one isn't already set.
            CaveCarrotSeed.setIndex();
            CaveCarrot.setIndex();
            CaveCarrot.setCropIndex();
            if (asset.AssetNameEquals("Maps\\springobjects"))
            {
                IAssetDataForImage editor    = asset.AsImage();
                Texture2D          data      = editor.Data;
                Texture2D          texture2D = new Texture2D(Game1.graphics.GraphicsDevice, data.Width, Math.Max(data.Height, 4096));
                editor.ReplaceWith(texture2D);
                editor.PatchImage(data, new Rectangle?(), new Rectangle?(), PatchMode.Replace);
                try
                {
                    editor.PatchImage(TextureSet.voidShroomSpore, new Rectangle?(), new Rectangle?(this.objectRect(VoidshroomSpore.getIndex())), PatchMode.Replace);
                    editor.PatchImage(TextureSet.caveCarrotSeed, new Rectangle?(), new Rectangle?(this.objectRect(CaveCarrotSeed.getIndex())), PatchMode.Replace);
                }
                catch (Exception)
                {
                }
            }
            else if (asset.AssetNameEquals("Data\\ObjectInformation"))
            {
                IAssetDataForDictionary <int, string> editor = asset.AsDictionary <int, string>();

                IDictionary <int, string> data = editor.Data;
                if (!data.ContainsKey(VoidshroomSpore.getIndex()))
                {
                    int    voidShroomSporeIndex = VoidshroomSpore.getIndex();
                    String voidShroomSpore      = VoidshroomSpore.getObjectData();
                    this.log("Add voidshroom spore object data: " + voidShroomSporeIndex + ": " + voidShroomSpore);
                    data.Add(voidShroomSporeIndex, voidShroomSpore);
                }

                if (!data.ContainsKey(CaveCarrotSeed.getIndex()))
                {
                    int    caveCarrotSeedIndex = CaveCarrotSeed.getIndex();
                    String caveCarrotObject    = CaveCarrotSeed.getObjectData();
                    this.log("Add cave carrot seed object data: " + caveCarrotSeedIndex + ": " + caveCarrotObject);
                    data.Add(caveCarrotSeedIndex, caveCarrotObject);
                }

                if (!data.ContainsKey(CaveCarrotFlower.getIndex()))
                {
                    int    caveCarrotFlowerIndex  = CaveCarrotFlower.getIndex();
                    String caveCarrotFlowerObject = CaveCarrotFlower.getObjectData();
                    this.log("Add cave carrot flower 'seed' data: " + caveCarrotFlowerIndex + ": " + caveCarrotFlowerObject);
                    data.Add(caveCarrotFlowerIndex, caveCarrotFlowerObject);
                }
            }
            else if (asset.AssetNameEquals("Data\\Crops"))
            {
                IAssetDataForDictionary <int, string> editor = asset.AsDictionary <int, string>();
                IDictionary <int, string>             data   = editor.Data;

                int seedIndex = CaveCarrot.getIndex();
                this.log("seedIndex is: " + seedIndex);
                if (!data.ContainsKey(seedIndex))
                {
                    String cropData = CaveCarrot.getCropData();
                    this.monitor.Log("Loading crop data: " + cropData);
                    data.Add(CaveCarrot.getIndex(), cropData);
                }

                int caveCarrotFlowerIndex = CaveCarrotFlower.getIndex();
                this.log("seedIndex is: " + caveCarrotFlowerIndex);
                if (!data.ContainsKey(caveCarrotFlowerIndex))
                {
                    String cropData = CaveCarrotFlower.getCropData();
                    this.monitor.Log("Loading crop data: " + cropData);
                    data.Add(caveCarrotFlowerIndex, cropData);
                }
            }
            else if (asset.AssetNameEquals("TileSheets\\crops"))
            {
                IAssetDataForImage editor    = asset.AsImage();
                Texture2D          data      = editor.Data;
                Texture2D          texture2D = new Texture2D(Game1.graphics.GraphicsDevice, data.Width, Math.Max(data.Height, 4096));
                editor.ReplaceWith(texture2D);
                editor.PatchImage(data, new Rectangle?(), new Rectangle?(), PatchMode.Replace);
                try
                {
                    int index = CaveCarrot.getCropIndex();
                    this.monitor.Log("Loading cave carrot crop texture.  Crop index: " + index);
                    editor.PatchImage(TextureSet.caveCarrotCrop, new Rectangle?(), new Rectangle?(this.cropRect(index)), PatchMode.Replace);

                    index = CaveCarrotFlower.getCropIndex();
                    this.monitor.Log("Loading cave carrot flower crop texture.  Crop index: " + index);
                    editor.PatchImage(TextureSet.caveCarrotFlowerCrop, new Rectangle?(), new Rectangle?(this.cropRect(index)), PatchMode.Replace);
                }
                catch (Exception)
                {
                }
            }
        }
Exemple #8
0
        public void Edit <T>(IAssetData asset)
        {
            if (asset.DataType == typeof(Map))
            {
                var map       = asset.GetData <Map>();
                var nameSplit = asset.AssetName.Split('\\');
                var name      = nameSplit[nameSplit.Length - 1];

                // Edit maps containing secret base entrances
                var numBasesInThisLocation = ModConsts.BaseEntryLocations.Count(_ => _.Value.Equals(name));
                if (numBasesInThisLocation <= 0)
                {
                    return;
                }
                Log.D($"Patching in {numBasesInThisLocation} secret bases to {name}.",
                      ModEntry.Instance.Config.DebugMode);
                ModEntry.EditVanillaMap(map, name);
            }
            else if (!asset.AssetNameEquals("LooseSprites\\Cursors"))
            {
            }
            else
            {
                // Home-cook a notification icon for under the HUD money tray:

                // Prime a canvas as a clipboard to hold each a copy of the vanilla icon
                // and our custom icon to merge together into a target open space in Cursors
                const int iconW           = 11;
                const int iconH           = 14;
                var       data            = asset.AsImage().Data;
                var       canvas          = new Color[iconW * iconH];
                var       texture         = new Texture2D(Game1.graphics.GraphicsDevice, iconW, iconH);
                var       vanillaIconArea = new Rectangle(383, 493, iconW, iconH);
                var       targetArea      = new Rectangle(IconLocation.X, IconLocation.Y, iconW, iconH);

                // Patch in a copy of the vanilla quest log icon
                data.GetData(0, vanillaIconArea, canvas, 0, canvas.Length);
                texture.SetData(canvas);
                asset.AsImage().PatchImage(texture, null, targetArea, PatchMode.Replace);

                // Chroma-key our custom icon with colours from the vanilla icon
                var colorSampleA = canvas[iconW * 5 + 1];
                var colorSampleB = canvas[iconW * 11 + 1];

                var colorR = new Color(255, 0, 0);
                var colorC = new Color(255, 0, 255);
                var colorG = new Color(0, 255, 0);
                var colorA = new Color(0, 0, 0, 0);

                var icon = _helper.Content.Load <Texture2D>(Path.Combine(
                                                                ModConsts.AssetsPath, ModConsts.OutdoorsStuffTilesheetId + ".png"));
                icon.GetData(0, new Rectangle(0, 0, iconW, iconH),
                             canvas, 0, canvas.Length);

                for (var i = 0; i < canvas.Length; ++i)
                {
                    if (canvas[i] == colorC)
                    {
                        canvas[i] = colorA;
                    }
                    else if (canvas[i] == colorG)
                    {
                        canvas[i] = colorSampleA;
                    }
                    else if (canvas[i] == colorR)
                    {
                        canvas[i] = colorSampleB;
                    }
                }

                // Patch in the custom icon over the vanilla icon copy
                texture.SetData(canvas);
                asset.AsImage().PatchImage(texture, null, targetArea, PatchMode.Overlay);

                // Patch in an alpha-shaded copy of the custom icon to use for the pulse animation
                var colorShade = new Color(0, 0, 0, 0.35f);

                for (var i = 0; i < canvas.Length; ++i)
                {
                    if (canvas[i] == colorSampleB)
                    {
                        canvas[i] = colorShade;
                    }
                    else if (canvas[i] == colorSampleA)
                    {
                        canvas[i] = colorA;
                    }
                }

                texture.SetData(canvas);
                asset.AsImage().PatchImage(texture, null,
                                           new Rectangle(targetArea.X - targetArea.Width, targetArea.Y, targetArea.Width, targetArea.Height),
                                           PatchMode.Overlay);
            }
        }
 public void Edit <T>(IAssetData asset)
 {
     this.monitor.Log($"Reloading {asset.AssetName}");
     this.AssetLoading?.Invoke(this, asset);
 }
Exemple #10
0
        /// <summary>Edit a matched asset.</summary>
        /// <param name="asset">A helper which encapsulates metadata about an asset and enables changes to it.</param>
        public void Edit <T>(IAssetData asset)
        {
            Monitor.Log("Editing asset " + asset.AssetName);
            if (asset.AssetNameEquals("Data/Events/HaleyHouse"))
            {
                IDictionary <string, string> data = asset.AsDictionary <string, string>().Data;

                data["195012/f Haley 2500/f Emily 2500/f Penny 2500/f Abigail 2500/f Leah 2500/f Maru 2500/o Abigail/o Penny/o Leah/o Emily/o Maru/o Haley/o Shane/o Harvey/o Sebastian/o Sam/o Elliott/o Alex/e 38/e 2123343/e 10/e 901756/e 54/e 15/k 195019"] = Regex.Replace(data["195012/f Haley 2500/f Emily 2500/f Penny 2500/f Abigail 2500/f Leah 2500/f Maru 2500/o Abigail/o Penny/o Leah/o Emily/o Maru/o Haley/o Shane/o Harvey/o Sebastian/o Sam/o Elliott/o Alex/e 38/e 2123343/e 10/e 901756/e 54/e 15/k 195019"], "(pause 1000/speak Maru \\\")[^$]+.a\\\"", $"$1{PHelper.Translation.Get("confrontation-female")}$h\"/emote Haley 21 true/emote Emily 21 true/emote Penny 21 true/emote Maru 21 true/emote Leah 21 true/emote Abigail 21").Replace("/dump girls 3", "");
                data["choseToExplain"]  = Regex.Replace(data["choseToExplain"], "(pause 1000/speak Maru \\\")[^$]+.a\\\"", $"$1{PHelper.Translation.Get("confrontation-female")}$h\"/emote Haley 21 true/emote Emily 21 true/emote Penny 21 true/emote Maru 21 true/emote Leah 21 true/emote Abigail 21").Replace("/dump girls 4", "");
                data["lifestyleChoice"] = Regex.Replace(data["lifestyleChoice"], "(pause 1000/speak Maru \\\")[^$]+.a\\\"", $"$1{PHelper.Translation.Get("confrontation-female")}$h\"/emote Haley 21 true/emote Emily 21 true/emote Penny 21 true/emote Maru 21 true/emote Leah 21 true/emote Abigail 21").Replace("/dump girls 4", "");
            }
            else if (asset.AssetNameEquals("Data/Events/Saloon"))
            {
                IDictionary <string, string> data = asset.AsDictionary <string, string>().Data;

                string aData = data["195013/f Shane 2500/f Sebastian 2500/f Sam 2500/f Harvey 2500/f Alex 2500/f Elliott 2500/o Abigail/o Penny/o Leah/o Emily/o Maru/o Haley/o Shane/o Harvey/o Sebastian/o Sam/o Elliott/o Alex/e 911526/e 528052/e 9581348/e 43/e 384882/e 233104/k 195099"];
                data["195013/f Shane 2500/f Sebastian 2500/f Sam 2500/f Harvey 2500/f Alex 2500/f Elliott 2500/o Abigail/o Penny/o Leah/o Emily/o Maru/o Haley/o Shane/o Harvey/o Sebastian/o Sam/o Elliott/o Alex/e 911526/e 528052/e 9581348/e 43/e 384882/e 233104/k 195099"] = Regex.Replace(aData, "(pause 1000/speak Sam \\\")[^$]+.a\\\"", $"$1{PHelper.Translation.Get("confrontation-male")}$h\"/emote Shane 21 true/emote Sebastian 21 true/emote Sam 21 true/emote Harvey 21 true/emote Alex 21 true/emote Elliott 21").Replace("/dump guys 3", "");
                aData = data["choseToExplain"];
                data["choseToExplain"] = Regex.Replace(aData, "(pause 1000/speak Sam \\\")[^$]+.a\\\"", $"$1{PHelper.Translation.Get("confrontation-male")}$h\"/emote Shane 21 true/emote Sebastian 21 true/emote Sam 21 true/emote Harvey 21 true/emote Alex 21 true/emote Elliott 21").Replace("/dump guys 4", "");
                aData          = data["crying"];
                data["crying"] = Regex.Replace(aData, "(pause 1000/speak Sam \\\")[^$]+.a\\\"", $"$1{PHelper.Translation.Get("confrontation-male")}$h\"/emote Shane 21 true/emote Sebastian 21 true/emote Sam 21 true/emote Harvey 21 true/emote Alex 21 true/emote Elliott 21").Replace("/dump guys 4", "");
            }
            else if (asset.AssetNameEquals("Strings/StringsFromCSFiles"))
            {
                IDictionary <string, string> data = asset.AsDictionary <string, string>().Data;
                data["NPC.cs.3985"] = Regex.Replace(data["NPC.cs.3985"], @"\.\.\.\$s.+", $"$n#$b#$c 0.5#{data["ResourceCollectionQuest.cs.13681"]}#{data["ResourceCollectionQuest.cs.13683"]}");
                Monitor.Log($"NPC.cs.3985 is set to \"{data["NPC.cs.3985"]}\"");
            }
            else if (asset.AssetNameEquals("Data/animationDescriptions"))
            {
                IDictionary <string, string> data = asset.AsDictionary <string, string>().Data;
                List <string> sleepKeys           = data.Keys.ToList().FindAll((s) => s.EndsWith("_Sleep"));
                foreach (string key in sleepKeys)
                {
                    if (!data.ContainsKey(key.ToLower()))
                    {
                        Monitor.Log($"adding {key.ToLower()} to animationDescriptions");
                        data.Add(key.ToLower(), data[key]);
                    }
                }
            }
            else if (asset.AssetNameEquals("Data/EngagementDialogue"))
            {
                IDictionary <string, string> data = asset.AsDictionary <string, string>().Data;
                if (!config.RomanceAllVillagers)
                {
                    return;
                }
                Farmer f = Game1.player;
                if (f == null)
                {
                    return;
                }
                foreach (string friend in f.friendshipData.Keys)
                {
                    if (!data.ContainsKey(friend + "0"))
                    {
                        data[friend + "0"] = "";
                    }
                    if (!data.ContainsKey(friend + "1"))
                    {
                        data[friend + "1"] = "";
                    }
                }
            }
            else if (asset.AssetName.StartsWith("Characters/schedules") || asset.AssetName.StartsWith("Characters\\schedules"))
            {
                IDictionary <string, string> data = asset.AsDictionary <string, string>().Data;
                List <string> keys = new List <string>(data.Keys);
                foreach (string key in keys)
                {
                    data[$"marriage_{key}"] = data[key];
                }
            }
        }
 public void OnCreated(IAssetData assetData)
 {
 }
 /// <inheritdoc/>
 public void Edit <T>(IAssetData asset)
 {
 }
Exemple #13
0
        /// <summary>Edit a matched asset.</summary>
        /// <param name="asset">A helper which encapsulates metadata about an asset and enables changes to it.</param>
        public void Edit <T>(IAssetData asset)
        {
            if (!Context.IsWorldReady)
            {
                return;                                                                                                                  // If we aren't in-game, don't edit anything.
            }
            ModData modData = Helper.ReadJsonFile <ModData>("saves.json") ?? new ModData();                                              // Load save file, or create new one.

            ModData.ModDataSave dataSave = Array.Find <ModData.ModDataSave>(modData.saves, x => x.saveName == Constants.SaveFolderName); // Load data for specific save.

            bool elemNotFound = false;
            bool shouldUpdate = false;

            if (dataSave == null) // If the savefile element was not found
            {
                dataSave          = new ModData.ModDataSave();
                dataSave.saveName = Constants.SaveFolderName; // if element wasn't found, prepare one.
                elemNotFound      = true;
            }

            if (!dataSave.enabled)
            {
                return;                    // If RandomTastes isn't enabled for this save.
            }
            asset
            .AsDictionary <string, string>()
            .Set((id, data) =>
            {
                if (id.StartsWith("Universal_"))
                {
                    return(data);                                                                                                          // leave universal tastes alone
                }
                ModData.ModDataSave.ModDataEntry entry = Array.Find <ModData.ModDataSave.ModDataEntry>(dataSave.entries, x => x.id == id); // find entry

                bool entryNotFound = false;

                if (entry == null)     // If the entry was not found
                {
                    // generate new entry
                    entry         = GenerateTastes(id);
                    entryNotFound = true;
                }

                string[] fields = data.Split('/');

                for (int i = 0; i < 5; i++)
                {
                    List <int> selected = new List <int>();
                    int fieldIndex      = 1;

                    switch (i)
                    {
                    case 0:
                        selected   = new List <int>(entry.love);
                        fieldIndex = 1;
                        break;

                    case 1:
                        selected   = new List <int>(entry.like);
                        fieldIndex = 3;
                        break;

                    case 2:
                        selected   = new List <int>(entry.neutral);
                        fieldIndex = 9;
                        break;

                    case 3:
                        selected   = new List <int>(entry.dislike);
                        fieldIndex = 5;
                        break;

                    case 4:
                        selected   = new List <int>(entry.hate);
                        fieldIndex = 7;
                        break;

                    default:
                        this.Monitor.Log($"Uhh... This doesn't seem right... ({i})", LogLevel.Error);
                        break;
                    }

                    this.Monitor.Log($"{i} : {string.Join(" ", selected)}", LogLevel.Trace);

                    fields[fieldIndex] = string.Join(" ", selected);
                }

                if (entryNotFound)     // If the entry was not found
                {
                    // add entry to savefile
                    List <ModData.ModDataSave.ModDataEntry> entryList = new List <ModData.ModDataSave.ModDataEntry>(dataSave.entries);
                    entryList.Add(entry);
                    dataSave.entries = entryList.ToArray();
                    shouldUpdate     = true;
                }

                return(string.Join("/", fields));
            });

            if (elemNotFound) // If the savefile element was not found
            {
                // add entry to savefile
                List <ModData.ModDataSave> modSaves = new List <ModData.ModDataSave>(modData.saves);
                modSaves.Add(dataSave);
                modData.saves = modSaves.ToArray();
            }

            shouldUpdate = shouldUpdate || elemNotFound; // Calculate elemNotFound into shouldUpdate

            if (shouldUpdate)
            {
                this.Helper.WriteJsonFile("saves.json", modData); // Update mod data file
            }
        }
        public void Edit <T>(IAssetData asset)
        {
            var data = asset.AsDictionary <string, string>().Data;

            data["BestOfQOS.Letter1"] = I18n.BestOfQOS_Letter1().Replace("[days]", days.ToString()).Replace("[price]", price.ToString());
        }
Exemple #15
0
 public AssetService(IBaseData <FixedAsset> baseData, IAssetData dbConnectionAsset) : base(baseData)
 {
     _dbConnectionAsset = dbConnectionAsset;
 }
Exemple #16
0
 public void Edit <T>(IAssetData asset)
 {
     EditAsset(ref asset);             // eat that, ENC0036
 }
Exemple #17
0
 public void Edit <T>(IAssetData asset)
 {
     asset.AsDictionary <string, string>().Data.Add("Brewery", "388 600 390 800 348 5/11/6/5/5/-1/-1/Brewery/Brewery/A brewery! You can brew and age whatever you like here./Buildings/none/96/96/20/null/Farm/150000/false");
 }
Exemple #18
0
        private void EditAsset(ref IAssetData asset)
        {
            if (ModEntry.PrintRename)
            {
                Log.D($"Editing {asset.AssetName}",
                      ModEntry.Instance.Config.DebugMode);
            }
            if (asset.AssetNameEquals(@"Data/Bundles"))
            {
                // Make no changes for the new community centre bundle, but set our base values from the data
                // Do this even with community centre changes disabled, in case we join as farmhand to a player who has it enabled

                var data = asset.AsDictionary <string, string>().Data;
                Bundles.BundleStartIndex = 1 + data.Keys.ToList().Max(key => int.Parse(key.Split('/')[1]));
            }
            else if (asset.AssetNameEquals(@"Data/BigCraftablesInformation"))
            {
                var data = asset.AsDictionary <int, string>().Data;

                // Add localised names for new craftables
                foreach (var pair in data.Where(pair => pair.Value.Split('/')[0].StartsWith(ModEntry.ObjectPrefix)).ToList())
                {
                    var split    = pair.Value.Split('/');
                    var name     = split[0].Split(new[] { '.' }, 3);
                    var nameData = data[pair.Key];
                    split[4]       = i18n.Get($"item.{name[2]}.description").ToString();
                    split[8]       = i18n.Get($"item.{name[2]}.name").ToString();
                    data[pair.Key] = string.Join("/", split);
                    if (ModEntry.PrintRename)
                    {
                        Log.D($"Named craftable {name[2]} ({data[pair.Key].Split('/')[5]})", ModEntry.Instance.Config.DebugMode);
                    }
                }

                asset.AsDictionary <int, string>().ReplaceWith(data);
            }
            else if (asset.AssetNameEquals(@"Data/CookingRecipes"))
            {
                // Edit fields of vanilla recipes to use new ingredients

                if (ModEntry.JsonAssets == null || Game1.currentLocation == null)
                {
                    return;
                }

                var data = asset.AsDictionary <string, string>().Data;

                // Add localised names for new recipes
                // While this also happens in CookingMenu.ctor for English locales, it's efficient here for other locales
                foreach (var pair in data.Where(pair => pair.Key.StartsWith(ModEntry.ObjectPrefix)).ToList())
                {
                    var name     = pair.Key.Split(new[] { '.' }, 3);
                    var nameData = data[pair.Key];
                    var split    = new string[6];
                    data[pair.Key].Split('/').CopyTo(split, 0);
                    split[5]       = i18n.Get($"item.{name[2]}.name").ToString();
                    data[pair.Key] = string.Join("/", split);
                    if (ModEntry.PrintRename)
                    {
                        Log.D($"Named recipe {name[2]} ({data[pair.Key].Split('/')[5]})", ModEntry.Instance.Config.DebugMode);
                    }
                }
                asset.AsDictionary <string, string>().ReplaceWith(data);

                if (!Config.AddRecipeRebalancing)
                {
                    Log.D($"Did not edit {asset.AssetName}: New recipe scaling is disabled in config file.",
                          Config.DebugMode);
                    return;
                }

                try
                {
                    // Substitute in the actual custom ingredients for custom recipes if custom ingredients are enabled
                    var enabled = Config.AddNewCropsAndStuff;
                    Dictionary <string, string> recipeData = null;
                    if (enabled || ModEntry.UsingPPJACrops)
                    {
                        // Update recipe data for recipes planned to use vanilla objects or best-available common custom objects
                        recipeData = new Dictionary <string, string>
                        {
                            // Maki Roll: Sashimi 1 Seaweed 1 Rice 1
                            {
                                "Maki Roll",
                                "227 1 152 1 423 1"
                            },
                            // Coleslaw: Vinegar 1 Mayonnaise 1
                            {
                                "Coleslaw",
                                $"{ModEntry.JsonAssets.GetObjectId(ModEntry.CabbageName)} 1"
                                + " 419 1 306 1"
                            },
                            // Cookies: Flour 1 Category:Egg 1 Chocolate Bar 1
                            {
                                "Cookies",
                                "246 1 -5 1"
                                + $" {ModEntry.JsonAssets.GetObjectId(ModEntry.ChocolateName)} 1"
                            },
                            // Pizza: Flour 2 Tomato 2 Cheese 2
                            {
                                "Pizza",
                                "246 2 256 2 424 2"
                            },
                        };

                        // Update recipe data for recipes planned to use custom objects exclusive to this mod
                        if (enabled)
                        {
                            var exclusiveCustomData = new Dictionary <string, string>
                            {
                                // Pink Cake: Cake 1 Melon 1
                                {
                                    "Pink Cake",
                                    $"{ModEntry.JsonAssets.GetObjectId(ModEntry.ObjectPrefix + "cake")} 1"
                                    + " 254 1"
                                },
                                // Chocolate Cake: Cake 1 Chocolate Bar 1
                                {
                                    "Chocolate Cake",
                                    $"{ModEntry.JsonAssets.GetObjectId(ModEntry.ObjectPrefix + "cake")} 1"
                                    + $" {ModEntry.JsonAssets.GetObjectId(ModEntry.ChocolateName)} 1"
                                }
                            };
                            recipeData = recipeData.Union(exclusiveCustomData).ToDictionary(pair => pair.Key, pair => pair.Value);
                        }

                        foreach (var recipe in recipeData)
                        {
                            data[recipe.Key] = ModEntry.UpdateEntry(data[recipe.Key], new[] { recipe.Value });
                        }

                        if (ModEntry.PrintRename && recipeData != null)
                        {
                            Log.D(data.Where(pair => recipeData.ContainsKey(pair.Key))
                                  .Aggregate($"Edited {asset.AssetName}:", (s, pair) => $"{s}\n{pair.Key}: {pair.Value}"),
                                  ModEntry.Instance.Config.DebugMode);
                        }

                        recipeData = new Dictionary <string, string>
                        {
                            // Beet Burger: Bread 1 Beet 1 Onion 1 Red Cabbage 1
                            {
                                ModEntry.ObjectPrefix + "burger",
                                "216 1 284 1"
                                + $" {ModEntry.JsonAssets.GetObjectId(ModEntry.OnionName)} 1"
                                + " 266 1"
                            },
                            // Cabbage Pot: Cabbage 2 Onion 2
                            {
                                ModEntry.ObjectPrefix + "cabbagepot",
                                $"{ModEntry.JsonAssets.GetObjectId(ModEntry.CabbageName)} 2"
                                + $" {ModEntry.JsonAssets.GetObjectId(ModEntry.OnionName)} 2"
                            },
                            // Garden Pie: Flour 1 Cabbage 1 Onion 1 Tomato 1
                            {
                                ModEntry.ObjectPrefix + "gardenpie",
                                "246 1"
                                + $" {ModEntry.JsonAssets.GetObjectId(ModEntry.CabbageName)} 1"
                                + $" {ModEntry.JsonAssets.GetObjectId(ModEntry.OnionName)} 1"
                                + " 256 1"
                            },
                            // Hearty Stew: Carrot 2 Potato 1
                            {
                                ModEntry.ObjectPrefix + "stew",
                                $"{ModEntry.JsonAssets.GetObjectId(ModEntry.CarrotName)} 2"
                                + " 192 1"
                            },
                            // Hot Cocoa: Milk (Any) 1 Chocolate Bar 1
                            {
                                ModEntry.ObjectPrefix + "hotcocoa",
                                "-6 1"
                                + $" {ModEntry.JsonAssets.GetObjectId(ModEntry.ChocolateName)} 1"
                            },
                            // Hot Pot Roast: Cranberry Sauce 1 Roots Platter 1 Stuffing 1 Onion 1
                            {
                                ModEntry.ObjectPrefix + "roast",
                                "238 1 244 1 239 1"
                                + $" {ModEntry.JsonAssets.GetObjectId(ModEntry.OnionName)} 1"
                            },
                            // Hunter's Plate: Potato 1 Cabbage 1 Horseradish 1
                            {
                                ModEntry.ObjectPrefix + "hunters",
                                "192 1"
                                + $" {ModEntry.JsonAssets.GetObjectId(ModEntry.CabbageName)} 1"
                                + " 16 1"
                            },
                            // Kebab: Tortilla 1 Tomato 1 Cabbage 1
                            {
                                ModEntry.ObjectPrefix + "kebab",
                                "229 1 256 1"
                                + $" {ModEntry.JsonAssets.GetObjectId(ModEntry.CabbageName)} 1"
                            },
                            // Onion Soup: Onion 1 Garlic 1 Cheese 1
                            {
                                ModEntry.ObjectPrefix + "onionsoup",
                                $"{ModEntry.JsonAssets.GetObjectId(ModEntry.OnionName)} 1"
                                + " 248 1 424 1"
                            },
                            // Pineapple Skewers: Pineapple 1 Onion 1 Eggplant 1
                            {
                                ModEntry.ObjectPrefix + "skewers",
                                "832 1"
                                + $" {ModEntry.JsonAssets.GetObjectId(ModEntry.OnionName)} 1"
                                + " 272 1"
                            },
                            // Redberry Pie: Flour 1 Sugar 1 Redberries 3

                            /*{
                             *      ModEntry.ObjectPrefix + "redberrypie",
                             *      "246 1 245 1"
                             + $" {ModEntry.JsonAssets.GetObjectId(ModEntry.ObjectPrefix + "redberry")} 3"
                             + },*/
                            // Tropical Salad: Pineapple 1 Apple 1 Pomegranate 1
                            {
                                ModEntry.ObjectPrefix + "tropicalsalad",
                                "832 1 613 1 637 1"
                            },
                        };
                        if (recipeData != null)
                        {
                            foreach (var recipe in recipeData)
                            {
                                data[recipe.Key] = ModEntry.UpdateEntry(data[recipe.Key], new[] { recipe.Value });
                            }
                        }
                    }

                    foreach (var recipe in data.ToDictionary(pair => pair.Key, pair => pair.Value))
                    {
                        var recipeSplit = data[recipe.Key].Split('/');

                        // Remove Oil from all cooking recipes in the game
                        var ingredients = recipeSplit[0].Split(' ');
                        if (!ingredients.Contains("247"))
                        {
                            continue;
                        }

                        recipeSplit[0] = ModEntry.UpdateEntry(recipeSplit[0],
                                                              ingredients.Where((ingredient, i) =>
                                                                                ingredient != "247" && (i <= 0 || ingredients[i - 1] != "247")).ToArray(),
                                                              false, true, 0, ' ');
                        data[recipe.Key] = ModEntry.UpdateEntry(data[recipe.Key], recipeSplit, false, true);
                    }

                    asset.AsDictionary <string, string>().ReplaceWith(data);

                    if (ModEntry.PrintRename && recipeData != null)
                    {
                        Log.D(data.Where(pair => recipeData.ContainsKey(pair.Key))
                              .Aggregate($"Edited {asset.AssetName}:", (s, pair) => $"{s}\n{pair.Key}: {pair.Value}"),
                              ModEntry.Instance.Config.DebugMode);
                    }

                    Log.D(data.Aggregate("", (str, recipe) => $"{str}\n{recipe.Key}: {recipe.Value}"),
                          Config.DebugMode);
                }
                catch (Exception e) when(e is ArgumentException || e is NullReferenceException || e is KeyNotFoundException)
                {
                    Log.D($"Did not patch {asset.AssetName}: {(!Config.DebugMode ? e.Message : e.ToString())}",
                          Config.DebugMode);
                }

                return;
            }

            if (asset.AssetNameEquals(@"Data/mail"))
            {
                var data = asset.AsDictionary <string, string>().Data;
                data.Add(ModEntry.MailCookbookUnlocked, i18n.Get("mail.cookbook_unlocked"));

                // lol pan
                var whoops = "Umm, hello @."
                             + $"^There was a mix-up at the forge with your {i18n.Get("menu.cooking_equipment.name")}."
                             + $" This is a bit embarrassing, so I'll return your materials as an apology."
                             + "^Come back to the shop and we'll see about getting you that upgrade."
                             + "^ - Clint, the blacksmith"
                             + "^^^                     $ 1000g"
                             + " %item object 334 5 %% [#] Love of Cooking Meta Menu Mix-Up";
                data.Add(ModEntry.MailFryingPanWhoops, whoops);

                asset.ReplaceWith(data);

                return;
            }

            if (asset.AssetNameEquals(@"Data/ObjectInformation"))
            {
                // Edit fields of vanilla objects to revalue and recategorise some produce

                if (ModEntry.JsonAssets == null || ModEntry.IngredientBuffChart == null || Game1.currentLocation == null)
                {
                    return;
                }

                var data = asset.AsDictionary <int, string>().Data;

                // Add localised names and descriptions for new objects
                foreach (var pair in data.Where(pair => pair.Value.Split('/') is string[] split && split[0].StartsWith(ModEntry.ObjectPrefix)).ToList())
                {
                    var name = pair.Value.Split('/')[0].Split(new [] { '.' }, 3);
                    data[pair.Key] = ModEntry.UpdateEntry(data[pair.Key],
                                                          new[] { i18n.Get($"item.{name[2]}.name").ToString(),
                                                                  i18n.Get($"item.{name[2]}.description").ToString() },
                                                          false, false, 4);
                    if (ModEntry.PrintRename)
                    {
                        Log.D($"Named {name[2]} ({i18n.Get($"item.{name[2]}.name")})", ModEntry.Instance.Config.DebugMode);
                    }
                }
                asset.AsDictionary <int, string>().ReplaceWith(data);

                if (!Config.AddRecipeRebalancing)
                {
                    Log.D($"Did not edit {asset.AssetName}: New recipe scaling is disabled in config file.",
                          Config.DebugMode);
                    return;
                }

                try
                {
                    var objectData = new Dictionary <int, string[]>
                    {
                        { 206, new[] { null, null, "45" } },                                                          // Pizza
                        { 220, new[] { null, null, "60" } },                                                          // Chocolate Cake
                        { 221, new[] { null, null, "75" } },                                                          // Pink Cake
                        { 419, new[] { null, "220", "-300", "Basic -26" } },                                          // Vinegar
                        { 247, new[] { null, null, "-300", "Basic -26", null, i18n.Get("item.oil.description") } },   // Oil
                        { 432, new[] { null, null, "-300", null, null, i18n.Get("item.truffleoil.description") } },   // Truffle Oil
                        { 917, new[] { null, null, null, null, null, data[917].Split('/')[5].Split('.')[0] + '.' } }, // Qi Seasoning
                        //{ModEntry.JsonAssets.GetObjectId(ObjectPrefix + "sugarcane"), new[] {null, null, null, "Basic"}},
                    };

                    // Apply above recipe changes
                    foreach (var obj in objectData.Where(o => !ModEntry.ItemDefinitions["FoodsThatGiveLeftovers"].Contains(data[o.Key].Split('/')[0])))
                    {
                        data[obj.Key] = ModEntry.UpdateEntry(data[obj.Key], obj.Value);
                    }

                    if (Config.AddRecipeRebalancing)
                    {
                        RebuildBuffs(ref data);
                    }

                    asset.AsDictionary <int, string>().ReplaceWith(data);

                    if (ModEntry.PrintRename)
                    {
                        Log.D($"Edited {asset.AssetName}:" + data.Where(pair => objectData.ContainsKey(pair.Key))
                              .Aggregate("", (s, pair) => $"{s}\n{pair.Key}: {pair.Value}"),
                              ModEntry.Instance.Config.DebugMode);
                    }
                }
                catch (Exception e) when(e is ArgumentException || e is NullReferenceException || e is KeyNotFoundException)
                {
                    Log.D($"Did not patch {asset.AssetName}: {(!Config.DebugMode ? e.Message : e.ToString())}",
                          Config.DebugMode);
                }

                return;
            }

            if (asset.AssetNameEquals(@"Data/Events/Town"))
            {
                var data = asset.AsDictionary <string, string>().Data;

                var key   = data.Keys.FirstOrDefault(key => key.StartsWith("191393"));
                var value = data[key];
                data.Remove(key);
                data.Add("191393/n ccIsComplete/w sunny/H", value);

                asset.AsDictionary <string, string>().ReplaceWith(data);
            }

            if (asset.AssetNameEquals(@"Data/Monsters"))
            {
                if (ModEntry.JsonAssets == null || Game1.currentLocation == null)
                {
                    return;
                }
                if (!ModEntry.Instance.Config.AddNewCropsAndStuff)
                {
                    Log.D($"Did not edit {asset.AssetName}: New crops are disabled in config file.",
                          Config.DebugMode);
                    return;
                }
                if (!ModEntry.RedberriesEnabled)
                {
                    Log.D($"Did not edit {asset.AssetName}: Redberries not yet enabled in code.",
                          Config.DebugMode);
                    return;
                }

                try
                {
                    var data        = asset.AsDictionary <string, string>().Data;
                    var monsterData = new Dictionary <string, string[]>
                    {
                        { "Shadow Shaman", new[] { $"{ModEntry.JsonAssets.GetObjectId(ModEntry.ObjectPrefix + "redberry_seeds")} .0035"
                                                   + (ModEntry.NettlesEnabled ? $" {ModEntry.JsonAssets.GetObjectId(ModEntry.ObjectPrefix + "nettles")} .05" : "") } },
                        { "Wilderness Golem", new[] { $"{ModEntry.JsonAssets.GetObjectId(ModEntry.ObjectPrefix + "redberry_seeds")} .0065" } },
                        { "Mummy", new[] { $"{ModEntry.JsonAssets.GetObjectId(ModEntry.ObjectPrefix + "redberry_seeds")} .0022" } },
                        { "Pepper Rex", new[] { $"{ModEntry.JsonAssets.GetObjectId(ModEntry.ObjectPrefix + "redberry_seeds")} .02" } },
                    };
                    foreach (var monster in monsterData)
                    {
                        data[monster.Key] = ModEntry.UpdateEntry(data[monster.Key], monster.Value, append: true);
                    }

                    asset.AsDictionary <string, string>().ReplaceWith(data);

                    if (ModEntry.PrintRename)
                    {
                        Log.D($"Edited {asset.AssetName}:" + data.Where(pair => monsterData.ContainsKey(pair.Key))
                              .Aggregate("", (s, pair) => $"{s}\n{pair.Key}: {pair.Value}"),
                              ModEntry.Instance.Config.DebugMode);
                    }
                }
                catch (Exception e) when(e is ArgumentException || e is NullReferenceException || e is KeyNotFoundException)
                {
                    Log.D($"Did not patch {asset.AssetName}: {(!Config.DebugMode ? e.Message : e.ToString())}",
                          Config.DebugMode);
                }

                return;
            }

            if (asset.AssetNameEquals(@"LooseSprites/Cursors"))
            {
                // Home-cook a notification icon for under the HUD money tray:

                if (ModEntry.SpriteSheet == null)
                {
                    return;
                }

                // Prime a canvas as a clipboard to hold in sequence both a copy of the vanilla icon
                // and our custom icon to merge together into some particular open space in Cursors
                var data            = asset.AsImage().Data;
                var canvas          = new Color[NotificationIconTargetArea.Width * NotificationIconTargetArea.Height];
                var texture         = new Texture2D(Game1.graphics.GraphicsDevice, NotificationIconTargetArea.Width, NotificationIconTargetArea.Height);
                var vanillaIconArea = new Rectangle(383, 493, NotificationIconTargetArea.Width, NotificationIconTargetArea.Height);
                var targetArea      = NotificationIconTargetArea;

                // Patch in a copy of the vanilla quest log icon
                data.GetData(0, vanillaIconArea, canvas, 0, canvas.Length);
                texture.SetData(canvas);
                asset.AsImage().PatchImage(texture, null, targetArea, PatchMode.Replace);

                // Chroma-key our custom icon with colours from the vanilla icon
                var colorSampleA = canvas[NotificationIconTargetArea.Width * 5 + 1];
                var colorSampleB = canvas[NotificationIconTargetArea.Width * 11 + 1];

                var colorR = new Color(255, 0, 0);
                var colorC = new Color(255, 0, 255);
                var colorG = new Color(0, 255, 0);
                var colorA = new Color(0, 0, 0, 0);

                ModEntry.SpriteSheet.GetData(0, new Rectangle(0, 0, NotificationIconTargetArea.Width, NotificationIconTargetArea.Height), canvas, 0, canvas.Length);

                for (var i = 0; i < canvas.Length; ++i)
                {
                    if (canvas[i] == colorC)
                    {
                        canvas[i] = colorA;
                    }
                    else if (canvas[i] == colorG)
                    {
                        canvas[i] = colorSampleA;
                    }
                    else if (canvas[i] == colorR)
                    {
                        canvas[i] = colorSampleB;
                    }
                }

                // Patch in the custom icon over the vanilla icon copy
                texture.SetData(canvas);
                asset.AsImage().PatchImage(texture, null, targetArea, PatchMode.Overlay);

                // Patch in an alpha-shaded copy of the custom icon to use for the pulse animation
                var colorShade = new Color(0, 0, 0, 0.35f);

                for (var i = 0; i < canvas.Length; ++i)
                {
                    if (canvas[i] == colorSampleB)
                    {
                        canvas[i] = colorShade;
                    }
                    else if (canvas[i] == colorSampleA)
                    {
                        canvas[i] = colorA;
                    }
                }

                // Apply changes to the Cursors sheet
                texture.SetData(canvas);
                targetArea.X -= targetArea.Width;
                asset.AsImage().PatchImage(texture, null, targetArea, PatchMode.Overlay);
            }
            else if (asset.AssetNameEquals(@"LooseSprites/JunimoNote"))
            {
                // Add icons for a new community centre bundle

                if (!ModEntry.Instance.Config.AddCookingCommunityCentreBundles)
                {
                    Log.D($"Did not edit {asset.AssetName}: Community centre edits are disabled in config file.",
                          Config.DebugMode);
                    return;
                }

                var sourceArea = new Rectangle(160, 208, 32 * 3, 32);
                var destArea   = new Rectangle(544, 212, 32 * 3, 32);
                var destImage  = asset.AsImage();
                destImage.PatchImage(ModEntry.SpriteSheet, sourceArea, destArea, PatchMode.Replace);
                asset.ReplaceWith(destImage.Data);
            }
            else if (asset.AssetNameEquals(@"Maps/Beach") && false)
            {
                //if (!Config.AddCookingQuestline)
                {
                    Log.D($"Did not edit {asset.AssetName}: Cooking questline is disabled in config file.",
                          Config.DebugMode);
                    return;
                }

                // Add dock wares to the secret beach

                // . . .
            }
            else if (asset.AssetNameEquals(@"Maps/springobjects"))
            {
                // Patch in object icons where necessary

                if (ModEntry.JsonAssets == null)
                {
                    return;
                }

                if (true)
                {
                    return;
                }

                int       index;
                Rectangle sourceArea, destArea;
                var       destImage = asset.AsImage();

                // Pitta Bread
                index = ModEntry.JsonAssets.GetObjectId("Pitta Bread");
                if (index > 0)
                {
                    sourceArea = Game1.getSourceRectForStandardTileSheet(destImage.Data, 217, 16, 16);
                    destArea   = Game1.getSourceRectForStandardTileSheet(destImage.Data, index, 16, 16);
                    destImage.PatchImage(destImage.Data, sourceArea, destArea, PatchMode.Replace);
                }
                asset.ReplaceWith(destImage.Data);
            }
            else if (asset.AssetNameEquals(@"Maps/townInterior"))
            {
                // Patch in changes for the community centre

                if (!(Game1.currentLocation is StardewValley.Locations.CommunityCenter))
                {
                    return;
                }

                var image = asset.AsImage();

                // Openable fridge in the kitchen
                var destArea = Bundles.FridgeOpenedSpriteArea;                             // Target some unused area of the sheet for this location

                var sourceArea = new Rectangle(320, 224, destArea.Width, destArea.Height); // Apply base fridge sprite
                image.PatchImage(image.Data, sourceArea, destArea, PatchMode.Replace);

                sourceArea = new Rectangle(0, 192, 16, 32);                 // Patch in opened-door fridge sprite from mouseCursors sheet
                image.PatchImage(Game1.mouseCursors2, sourceArea, destArea, PatchMode.Overlay);

                // New star on the community centre bundle tracker wall
                if (!ModEntry.Instance.Config.AddCookingCommunityCentreBundles)
                {
                    Log.D($"Did not edit {asset.AssetName}: Community centre edits are disabled in config file.",
                          Config.DebugMode);
                }
                else
                {
                    sourceArea = new Rectangle(370, 705, 7, 7);
                    destArea   = new Rectangle(380, 710, sourceArea.Width, sourceArea.Height);
                    image.PatchImage(image.Data, sourceArea, destArea, PatchMode.Replace);
                }

                asset.ReplaceWith(image.Data);
            }
            else if (asset.AssetNameEquals(@"Strings/Locations"))
            {
                // Make changes to facilitate a new community centre bundle

                if (!ModEntry.Instance.Config.AddCookingCommunityCentreBundles)
                {
                    Log.D($"Did not edit {asset.AssetName}: Community centre edits are disabled in config file.",
                          Config.DebugMode);
                    return;
                }

                var data = asset.AsDictionary <string, string>().Data;

                // Add area name
                data["CommunityCenter_AreaName_" + Bundles.CommunityCentreAreaName] = i18n.Get("world.community_centre.kitchen");

                // Insert a new AreaCompletion line to account for our extra bundle
                const int newJunimoLineNumber = 3;
                for (var i = Bundles.CommunityCentreAreaNumber + 1; i > newJunimoLineNumber; --i)
                {
                    var below = data["CommunityCenter_AreaCompletion" + (i - 1)];
                    data["CommunityCenter_AreaCompletion" + i] = below;
                }
                data["CommunityCenter_AreaCompletion" + newJunimoLineNumber] = i18n.Get("world.community_centre.newjunimoline");

                asset.AsDictionary <string, string>().ReplaceWith(data);
            }
            else if (asset.AssetNameEquals(@"Strings/UI"))
            {
                // Make changes to facilitate a new community centre bundle

                if (!ModEntry.Instance.Config.AddCookingCommunityCentreBundles)
                {
                    Log.D($"Did not edit {asset.AssetName}: Community centre edits are disabled in config file.",
                          Config.DebugMode);
                    return;
                }

                var data = asset.AsDictionary <string, string>().Data;
                data["JunimoNote_Reward" + Bundles.CommunityCentreAreaName] = i18n.Get("world.community_centre.reward");
                asset.AsDictionary <string, string>().ReplaceWith(data);
            }
            else if (asset.AssetNameEquals(@"TileSheets/tools"))
            {
                // Patch in tool sprites for cooking equipment

                if (ModEntry.SpriteSheet == null)
                {
                    return;
                }

                if (!ModEntry.Instance.Config.AddCookingToolProgression)
                {
                    Log.D($"Did not edit {asset.AssetName}: Cooking equipment is disabled in config file.",
                          Config.DebugMode);
                    return;
                }

                var sourceArea = new Rectangle(192, 272, 16 * 4, 16);
                var destImage  = asset.AsImage();
                var destArea   = new Rectangle(272, 0, sourceArea.Width, sourceArea.Height);
                destImage.PatchImage(ModEntry.SpriteSheet, sourceArea, destArea, PatchMode.Replace);
                asset.ReplaceWith(destImage.Data);
            }
        }
Exemple #19
0
 public void Edit <T>(IAssetData asset)
 {
     this.Edit(asset);
 }
Exemple #20
0
 /// <summary>Apply the patch to a loaded asset.</summary>
 /// <typeparam name="T">The asset type.</typeparam>
 /// <param name="asset">The asset to edit.</param>
 /// <exception cref="NotSupportedException">The current patch type doesn't support editing assets.</exception>
 public virtual void Edit <T>(IAssetData asset)
 {
     throw new NotSupportedException("This patch type doesn't support loading assets.");
 }
Exemple #21
0
 public abstract void ApplyPatches(IAssetData data);
Exemple #22
0
 public void Edit<T>(IAssetData asset)
 {
     if (asset.AssetNameEquals(@"Data\Blueprints"))
         asset.AsDictionary<string, string>().Data.Add("Desert Obelisk",
             "337 10 768 10/3/3/-1/-1/-2/-1/null/Desert Obelisk/Warps you to the desert./Buildings/none/48/128/-1/null/Farm/1000000/true");
 }
Exemple #23
0
        public void Edit <T>(IAssetData asset)
        {
            if (!Mod.instance.didInit)
            {
                return;
            }

            if (asset.AssetNameEquals("Data\\NPCGiftTastes"))
            {
                var data = asset.AsDictionary <string, string>().Data;
                // TODO: This could be optimized from mn to... m + n?
                // Basically, iterate through objects and create Dictionary<NPC name, GiftData[]>
                // Iterate through objects, each section and add to dict[npc][approp. section]
                // Point is, I'm doing this the lazy way right now
                var newData = new Dictionary <string, string>(data);
                foreach (var npc in data)
                {
                    if (npc.Key.StartsWith("Universal_"))
                    {
                        continue;
                    }

                    string[] sections = npc.Value.Split('/');
                    if (sections.Length != 11)
                    {
                        Log.warn($"Bad gift taste data for {npc.Key}!");
                        continue;
                    }

                    string        loveStr    = sections[0];
                    List <string> loveIds    = new List <string>(sections[1].Split(' '));
                    string        likeStr    = sections[2];
                    List <string> likeIds    = new List <string>(sections[3].Split(' '));
                    string        dislikeStr = sections[4];
                    List <string> dislikeIds = new List <string>(sections[5].Split(' '));
                    string        hateStr    = sections[6];
                    List <string> hateIds    = new List <string>(sections[7].Split(' '));
                    string        neutralStr = sections[8];
                    List <string> neutralIds = new List <string>(sections[9].Split(' '));

                    foreach (var obj in Mod.instance.objects)
                    {
                        if (obj.GiftTastes == null)
                        {
                            continue;
                        }
                        if (obj.GiftTastes.Love != null && obj.GiftTastes.Love.Contains(npc.Key))
                        {
                            loveIds.Add(obj.GetObjectId().ToString());
                        }
                        if (obj.GiftTastes.Like != null && obj.GiftTastes.Like.Contains(npc.Key))
                        {
                            likeIds.Add(obj.GetObjectId().ToString());
                        }
                        if (obj.GiftTastes.Neutral != null && obj.GiftTastes.Neutral.Contains(npc.Key))
                        {
                            neutralIds.Add(obj.GetObjectId().ToString());
                        }
                        if (obj.GiftTastes.Dislike != null && obj.GiftTastes.Dislike.Contains(npc.Key))
                        {
                            dislikeIds.Add(obj.GetObjectId().ToString());
                        }
                        if (obj.GiftTastes.Hate != null && obj.GiftTastes.Hate.Contains(npc.Key))
                        {
                            hateIds.Add(obj.GetObjectId().ToString());
                        }
                    }

                    string loveIdStr    = string.Join(" ", loveIds);
                    string likeIdStr    = string.Join(" ", likeIds);
                    string dislikeIdStr = string.Join(" ", dislikeIds);
                    string hateIdStr    = string.Join(" ", hateIds);
                    string neutralIdStr = string.Join(" ", neutralIds);
                    newData[npc.Key] = $"{loveStr}/{loveIdStr}/{likeStr}/{likeIdStr}/{dislikeStr}/{dislikeIdStr}/{hateStr}/{hateIdStr}/{neutralStr}/{neutralIdStr}/ ";

                    Log.verbose($"Adding gift tastes for {npc.Key}: {newData[npc.Key]}");
                }
                asset.ReplaceWith(newData);
            }
        }
Exemple #24
0
        public void Edit <T>(IAssetData asset)
        {
            IDictionary <string, string> data = asset.AsDictionary <string, string>().Data;

            data["RegressionStart"] = "Dear @,^Welcome to town! Here are some veggies from the garden to tide you over while you move in! Also, sorry to be so forward, but living here might change you in ways you didn't expect. Just in case, I've also enclosed some... supplies. (You can buy more at Pierre's.) %item object 399 20 %% ^      <, Jodi";
        }
        /// <inheritdoc />
        public override void Edit <T>(IAssetData asset)
        {
            string errorPrefix = $"Can't apply map patch \"{this.Path}\" to {this.TargetAsset}";

            // validate
            if (typeof(T) != typeof(Map))
            {
                this.Monitor.Log($"{errorPrefix}: this file isn't a map file (found {typeof(T)}).", LogLevel.Warn);
                return;
            }
            if (this.AppliesMapPatch && !this.FromAssetExists())
            {
                this.Monitor.Log($"{errorPrefix}: the {nameof(PatchConfig.FromFile)} file '{this.FromAsset}' doesn't exist.", LogLevel.Warn);
                return;
            }

            // get map
            IAssetDataForMap targetAsset = asset.AsMap();
            Map target = targetAsset.Data;

            // apply map area patch
            if (this.AppliesMapPatch)
            {
                Map source = this.ContentPack.LoadAsset <Map>(this.FromAsset);
                if (!this.TryApplyMapPatch(source, targetAsset, out string error))
                {
                    this.Monitor.Log($"{errorPrefix}: map patch couldn't be applied: {error}", LogLevel.Warn);
                }
            }

            // patch map tiles
            if (this.AppliesTilePatches)
            {
                int i = 0;
                foreach (EditMapPatchTile tilePatch in this.MapTiles)
                {
                    i++;
                    if (!this.TryApplyTile(target, tilePatch, out string error))
                    {
                        this.Monitor.Log($"{errorPrefix}: {nameof(PatchConfig.MapTiles)} > entry {i} couldn't be applied: {error}", LogLevel.Warn);
                    }
                }
            }

            // patch map properties
            foreach (EditMapPatchProperty property in this.MapProperties)
            {
                string key   = property.Key.Value;
                string value = property.Value.Value;

                if (value == null)
                {
                    target.Properties.Remove(key);
                }
                else
                {
                    target.Properties[key] = value;
                }
            }

            // apply map warps
            if (this.AddWarps.Any())
            {
                this.ApplyWarps(target, out IDictionary <string, string> errors);
                foreach (var pair in errors)
                {
                    this.Monitor.Log($"{errorPrefix}: {nameof(PatchConfig.AddWarps)} > warp '{pair.Key}' couldn't be applied: {pair.Value}", LogLevel.Warn);
                }
            }

            // apply text operations
            for (int i = 0; i < this.TextOperations.Length; i++)
            {
                if (!this.TryApplyTextOperation(target, this.TextOperations[i], out string error))
                {
                    this.Monitor.Log($"{errorPrefix}: {nameof(PatchConfig.TextOperations)} > entry {i} couldn't be applied: {error}", LogLevel.Warn);
                }
            }
        }
Exemple #26
0
        /// <summary>The method that gets called foreach asset we want to change. Used to apply the new cursor sprite.</summary>
        /// <typeparam name="T">The asset type.</typeparam>
        /// <param name="asset">The asset data.</param>
        public void Edit <T>(IAssetData asset)
        {
            Texture2D cursorTexture = this.Helper.Content.Load <Texture2D>("assets/Cursor.png", ContentSource.ModFolder);

            asset.AsImage().PatchImage(cursorTexture, sourceArea: new Rectangle(0, 0, 126, 27), targetArea: new Rectangle(0, 0, 126, 27), patchMode: PatchMode.Replace);
        }
        /// <summary>Apply the patch to a list asset.</summary>
        /// <typeparam name="TValue">The list value type.</typeparam>
        /// <param name="asset">The asset to edit.</param>
        private void ApplyList <TValue>(IAssetData asset)
        {
            // get data
            IList <TValue> data = asset.GetData <List <TValue> >();

            TValue GetByKey(string key) => data.FirstOrDefault(p => this.GetKey(p) == key);

            // apply field/record edits
            this.ApplyCollection <string, TValue>(
                asset,
                hasEntry: key => GetByKey(key) != null,
                getEntry: key => GetByKey(key),
                setEntry: (key, value) =>
            {
                TValue match = GetByKey(key);
                if (match != null)
                {
                    int index = data.IndexOf(match);
                    data.RemoveAt(index);
                    data.Insert(index, value);
                }
                else
                {
                    data.Add(value);
                }
            },
                removeEntry: key =>
            {
                TValue match = GetByKey(key);
                if (match != null)
                {
                    int index = data.IndexOf(match);
                    data.RemoveAt(index);
                }
            }
                );

            // apply moves
            foreach (EditDataPatchMoveRecord moveRecord in this.MoveRecords)
            {
                if (!moveRecord.IsReady)
                {
                    continue;
                }
                string errorLabel = $"record \"{this.Path}\" > {nameof(PatchConfig.MoveEntries)} > \"{moveRecord.ID}\"";

                // get entry
                TValue entry = GetByKey(moveRecord.ID.Value);
                if (entry == null)
                {
                    this.Monitor.LogOnce($"Can't move {errorLabel}: no entry with that ID exists.", LogLevel.Warn);
                    continue;
                }
                int fromIndex = data.IndexOf(entry);

                // move to position
                if (moveRecord.ToPosition == MoveEntryPosition.Top)
                {
                    data.RemoveAt(fromIndex);
                    data.Insert(0, entry);
                }
                else if (moveRecord.ToPosition == MoveEntryPosition.Bottom)
                {
                    data.RemoveAt(fromIndex);
                    data.Add(entry);
                }
                else if (moveRecord.AfterID.IsMeaningful() || moveRecord.BeforeID.IsMeaningful())
                {
                    // get config
                    bool   isAfterID = moveRecord.AfterID.IsMeaningful();
                    string anchorID  = isAfterID ? moveRecord.AfterID.Value : moveRecord.BeforeID.Value;
                    errorLabel += $" {(isAfterID ? nameof(PatchMoveEntryConfig.AfterID) : nameof(PatchMoveEntryConfig.BeforeID))} \"{anchorID}\"";

                    // get anchor entry
                    TValue anchorEntry = GetByKey(anchorID);
                    if (anchorEntry == null)
                    {
                        this.Monitor.LogOnce($"Can't move {errorLabel}: no entry with the relative ID exists.", LogLevel.Warn);
                        continue;
                    }
                    if (object.ReferenceEquals(entry, anchorEntry))
                    {
                        this.Monitor.LogOnce($"Can't move {errorLabel}: can't move entry relative to itself.", LogLevel.Warn);
                        continue;
                    }

                    // move record
                    data.RemoveAt(fromIndex);
                    int newIndex = data.IndexOf(anchorEntry);
                    data.Insert(isAfterID ? newIndex + 1 : newIndex, entry);
                }
            }
        }
Exemple #28
0
        /// <inheritdoc />
        public override void Edit <T>(IAssetData asset)
        {
            string errorPrefix = $"Can't apply image patch \"{this.Path}\" to {this.TargetAsset}";

            // validate
            if (typeof(T) != typeof(Texture2D))
            {
                this.Monitor.Log($"{errorPrefix}: this file isn't an image file (found {typeof(T)}).", LogLevel.Warn);
                return;
            }
            if (!this.FromAssetExists())
            {
                this.Monitor.Log($"{errorPrefix}: the {nameof(PatchConfig.FromFile)} file '{this.FromAsset}' doesn't exist.", LogLevel.Warn);
                return;
            }

            // fetch data
            IAssetDataForImage editor = asset.AsImage();
            Texture2D          source = this.ContentPack.LoadAsset <Texture2D>(this.FromAsset);

            if (!this.TryReadArea(this.FromArea, 0, 0, source.Width, source.Height, out Rectangle sourceArea, out string error))
            {
                this.Monitor.Log($"{errorPrefix}: the source area is invalid: {error}.", LogLevel.Warn);
                return;
            }
            if (!this.TryReadArea(this.ToArea, 0, 0, sourceArea.Width, sourceArea.Height, out Rectangle targetArea, out error))
            {
                this.Monitor.Log($"{errorPrefix}: the target area is invalid: {error}.", LogLevel.Warn);
                return;
            }

            // validate error conditions
            if (sourceArea.X < 0 || sourceArea.Y < 0 || sourceArea.Width < 0 || sourceArea.Height < 0)
            {
                this.Monitor.Log($"{errorPrefix}: source area (X:{sourceArea.X}, Y:{sourceArea.Y}, Width:{sourceArea.Width}, Height:{sourceArea.Height}) has negative values, which isn't valid.", LogLevel.Error);
                return;
            }
            if (targetArea.X < 0 || targetArea.Y < 0 || targetArea.Width < 0 || targetArea.Height < 0)
            {
                this.Monitor.Log($"{errorPrefix}: target area (X:{targetArea.X}, Y:{targetArea.Y}, Width:{targetArea.Width}, Height:{targetArea.Height}) has negative values, which isn't valid.", LogLevel.Error);
                return;
            }
            if (targetArea.Right > editor.Data.Width)
            {
                this.Monitor.Log($"{errorPrefix}: target area (X:{targetArea.X}, Y:{targetArea.Y}, Width:{targetArea.Width}, Height:{targetArea.Height}) extends past the right edge of the image (Width:{editor.Data.Width}), which isn't allowed. Patches can only extend the tilesheet downwards.", LogLevel.Error);
                return;
            }
            if (sourceArea.Width != targetArea.Width || sourceArea.Height != targetArea.Height)
            {
                string sourceAreaLabel = this.FromArea != null ? $"{nameof(this.FromArea)}" : "source image";
                string targetAreaLabel = this.ToArea != null ? $"{nameof(this.ToArea)}" : "target image";
                this.Monitor.Log($"{errorPrefix}: {sourceAreaLabel} size (Width:{sourceArea.Width}, Height:{sourceArea.Height}) doesn't match {targetAreaLabel} size (Width:{targetArea.Width}, Height:{targetArea.Height}).", LogLevel.Error);
                return;
            }

            // extend tilesheet if needed
            this.ResizedLastImage = editor.ExtendImage(editor.Data.Width, targetArea.Bottom);

            // apply source image
            editor.PatchImage(source, sourceArea, targetArea, this.PatchMode);
        }
 /// <summary>
 /// Constructor.
 /// </summary>
 /// <param name="entry">Instance of ModEntry.</param>
 /// <param name="packHelper">Instance of ContentPackHelper</param>
 /// <param name="asset">Current asset being edited.</param>
 public HairEditor(ModEntry entry, ContentPackHelper packHelper, IAssetData asset)
 {
     Entry      = entry;
     PackHelper = packHelper;
     Asset      = asset;
 }
        /// <summary>Edit the evaluation entries in Strings\StringsFromCSFiles with new dialogues and tokens.</summary>
        /// <typeparam name="_T">The asset Type</typeparam>
        /// <param name="asset">A helper which encapsulates metadata about an asset and enables changes to it</param>
        public void Edit <_T> (IAssetData asset)
        {
            // Can't edit these assets without an active game for spouse, NPC, and year info

            if (!Context.IsWorldReady)
            {
                return;
            }

            // Prepare tokens

            string pastYears;
            int    yearsPassed = Max(Game1.year - 1, Config.YearsBeforeEvaluation);          // Accurate dialogue even for delayed event

            if (yearsPassed >= 10)
            {
                if (Config.GrandpaDialogue == "Nuclear")
                {
                    pastYears = i18n.Get("GrandpaDuringManyYears.Nuclear");
                }
                else
                {
                    pastYears = i18n.Get("GrandpaDuringManyYears");
                }
            }
            else             // yearsPassed < 10
            {
                pastYears = i18n.Get("GrandpaDuringPastYears").ToString().Split('|')[yearsPassed];
            }

            string spouseOrLewis;

            if (Game1.player.isMarried())
            {
                spouseOrLewis = "%spouse";
            }
            else
            {
                spouseOrLewis = Game1.getCharacterFromName <NPC>("Lewis").displayName;
            }

            string fifthCandle = "";
            bool   inOneYear   = Game1.year == 1 || (Game1.year == 2 && Game1.currentSeason == "spring" && Game1.dayOfMonth == 1);

            if (Utility.getGrandpaScore() >= 21 && inOneYear)
            {
                fifthCandle = i18n.Get("FifthCandle." + Config.GrandpaDialogue);
            }

            // Collect all portrait tokens & others

            var allEvaluationTokens = new Dictionary <string, string>(Config.PortraitTokens)
            {
                ["pastYears"]     = pastYears,
                ["spouseOrLewis"] = spouseOrLewis,
                ["fifthCandle"]   = fifthCandle
            };

            // Prepare data

            var data = asset.AsDictionary <string, string>().Data;

            // Main patching loop

            if (asset.AssetNameEquals($"Strings\\StringsFromCSFiles"))
            {
                foreach (string entry in EvaluationStrings)
                {
                    string gameKey = i18n.Get(entry + ".gameKey");
                    string modKey  = entry + "." + Config.GrandpaDialogue;
                    if (Config.GenderNeutrality)
                    {
                        modKey += "-gn";
                    }
                    string value = i18n.Get(modKey, allEvaluationTokens);

                    data[gameKey] = value;
                }
            }
        }