/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="name">The unique location name.</param> /// <param name="fromMapFile">The initial map file to load.</param> /// <param name="migrateLegacyNames">The fallback location names to migrate if no location is found matching <paramref name="name"/>.</param> /// <param name="contentPack">The content pack which added the location.</param> public CustomLocationData(string name, string fromMapFile, string[] migrateLegacyNames, IContentPack contentPack) { this.Name = name; this.FromMapFile = fromMapFile; this.MigrateLegacyNames = migrateLegacyNames; this.ContentPack = contentPack; this.PublicMapPath = PathUtilities.NormalizeAssetName($"Maps/{name}"); this.IsEnabled = true; this.HasLegacyNames = this.MigrateLegacyNames.Any(); }
/// <summary>Load data from a version 1.1 manifest.</summary> /// <param name="contentPack">The content pack to load.</param> /// <param name="configPath">The content pack's relative config file path.</param> private LocationConfig ReadConfig_1_2(IContentPack contentPack, string configPath) { try { // read raw data LocationConfig config = contentPack.ReadJsonFile <LocationConfig>(configPath); // load child config if (config.Includes.Any()) { foreach (string include in config.Includes) { // parse file string childPath = $"{include}.json"; LocationConfig childConfig; try { childConfig = contentPack.LoadAsset <LocationConfig>(childPath); } catch (Exception err) { this.Monitor.Log($" Skipped child config '{childPath}' because it can't be parsed.", LogLevel.Error, err); continue; } // add data to parent config foreach (Conditional entry in childConfig.Conditionals) { config.Conditionals.Add(entry); } foreach (Location entry in childConfig.Locations) { config.Locations.Add(entry); } foreach (Override entry in childConfig.Overrides) { config.Overrides.Add(entry); } foreach (Property entry in childConfig.Properties) { config.Properties.Add(entry); } foreach (Redirect entry in childConfig.Redirects) { config.Redirects.Add(entry); } foreach (string entry in childConfig.Shops) { config.Shops.Add(entry); } foreach (TeleporterList entry in childConfig.Teleporters) { config.Teleporters.Add(entry); } foreach (Tile entry in childConfig.Tiles) { config.Tiles.Add(entry); } foreach (Tilesheet entry in childConfig.Tilesheets) { config.Tilesheets.Add(entry); } foreach (Warp entry in childConfig.Warps) { config.Warps.Add(entry); } } } return(config); } catch (Exception err) { this.Monitor.Log(" Skipped: can't parse config file (version 1.2).", LogLevel.Warn, err); return(null); } }
/// <summary> /// Initializes a new instance of the <see cref="ContentPackAssetProvider"/> class. /// </summary> /// <param name="contentPack">The content pack to provide assets for.</param> public ContentPackAssetProvider(IContentPack contentPack) { this.contentPack = contentPack; }
/********* ** Protected methods *********/ /// <summary>Construct an instance.</summary> /// <param name="path">The path to the patch from the root content file.</param> /// <param name="type">The patch type.</param> /// <param name="assetName">The normalized asset name to intercept.</param> /// <param name="conditions">The conditions which determine whether this patch should be applied.</param> /// <param name="updateRate">When the patch should be updated.</param> /// <param name="normalizeAssetName">Normalize an asset name.</param> /// <param name="contentPack">The content pack which requested the patch.</param> /// <param name="parentPatch">The parent <see cref="PatchType.Include"/> patch for which this patch was loaded, if any.</param> /// <param name="fromAsset">The normalized asset key from which to load the local asset (if applicable), including tokens.</param> protected Patch(LogPathBuilder path, PatchType type, IManagedTokenString assetName, IEnumerable <Condition> conditions, UpdateRate updateRate, IContentPack contentPack, IPatch parentPatch, Func <string, string> normalizeAssetName, IManagedTokenString fromAsset = null) { this.Path = path; this.Type = type; this.ManagedRawTargetAsset = assetName; this.Conditions = conditions.ToArray(); this.UpdateRate = updateRate; this.NormalizeAssetNameImpl = normalizeAssetName; this.PrivateContext = new LocalContext(scope: contentPack.Manifest.UniqueID); this.ManagedRawFromAsset = fromAsset; this.ContentPack = contentPack; this.ParentPatch = parentPatch; this.Contextuals .Add(this.Conditions) .Add(assetName) .Add(fromAsset); this.ManuallyUpdatedTokens.Add(assetName); this.ManuallyUpdatedTokens.Add(fromAsset); }
/// <summary>Validate the configuration for a content pack and remove invalid settings.</summary> /// <param name="contentPack">The content pack to load.</param> /// <param name="config">The config settings.</param> private ContentPackData ValidateData(IContentPack contentPack, LocationConfig config) { ContentPackData data = new ContentPackData { ContentPack = contentPack }; string currentStep = "Entry"; try { // validate locations if (config.Locations != null) { currentStep = "Locations"; foreach (Location location in config.Locations) { if (!this.AssertFileExists(contentPack, "location", location.FileName)) { continue; } if (!this.AffectedLocations.Add(location.MapName)) { this.Monitor.Log($" Skipped {location}: that map is already being modified.", LogLevel.Error); continue; } if (!this.LocationTypes.Contains(location.Type)) { this.Monitor.Log($" Location {location} has unknown type, using 'Default' instead.", LogLevel.Warn); location.Type = "Default"; } data.Locations.Add(location); } } // validate overrides if (config.Overrides != null) { currentStep = "Overrides"; foreach (Override @override in config.Overrides) { if (!this.AssertFileExists(contentPack, "override", @override.FileName)) { continue; } if (!this.AffectedLocations.Add(@override.MapName)) { this.Monitor.Log($" Skipped {@override}: that map is already being modified.", LogLevel.Error); continue; } data.Overrides.Add(@override); } } // validate redirects if (config.Redirects != null) { currentStep = "Redirects"; foreach (Redirect redirect in config.Redirects) { if (!File.Exists(Path.Combine(Game1.content.RootDirectory, $"{redirect.FromFile}.xnb"))) { this.Monitor.Log($" Skipped {redirect}: file {redirect.FromFile}.xnb doesn't exist in the game's content folder.", LogLevel.Error); continue; } if (!this.AssertFileExists(contentPack, "redirect", redirect.ToFile)) { continue; } data.Redirects.Add(redirect); } } // validate tilesheets if (config.Tilesheets != null) { currentStep = "Tilesheets"; foreach (Tilesheet tilesheet in config.Tilesheets) { if (tilesheet.FileName != null) { if (tilesheet.Seasonal) { bool filesExist = this.AssertFileExists(contentPack, "tilesheet", $"{tilesheet.FileName}_spring") && this.AssertFileExists(contentPack, "tilesheet", $"{tilesheet.FileName}_summer") && this.AssertFileExists(contentPack, "tilesheet", $"{tilesheet.FileName}_fall") && this.AssertFileExists(contentPack, "tilesheet", $"{tilesheet.FileName}_winter"); if (!filesExist) { continue; } } else if (!this.AssertFileExists(contentPack, "tilesheet", tilesheet.FileName)) { continue; } } data.Tilesheets.Add(tilesheet); } } // validate tiles if (config.Tiles != null) { currentStep = "Tiles"; foreach (Tile tile in config.Tiles) { if (!this.ValidLayers.Contains(tile.LayerId)) { this.Monitor.Log($" Skipped {tile}: unknown layer '{tile.LayerId}'.", LogLevel.Error); continue; } data.Tiles.Add(tile); } } // validate properties if (config.Properties != null) { currentStep = "Properties"; foreach (Property property in config.Properties) { if (!this.ValidLayers.Contains(property.LayerId)) { this.Monitor.Log($" Skipped `{property}`: unknown layer '{property.LayerId}'.", LogLevel.Error); continue; } data.Properties.Add(property); } } // validate warps if (config.Warps != null) { currentStep = "Warps"; foreach (Warp warp in config.Warps) { data.Warps.Add(warp); } } // validate conditionals if (config.Conditionals != null) { currentStep = "Conditionals"; foreach (Conditional condition in config.Conditionals) { if (condition.Item < -1) { this.Monitor.Log($" Skipped {condition}, references null item.", LogLevel.Error); continue; } if (condition.Amount < 1) { this.Monitor.Log($" Skipped {condition}, item amount can't be less then 1.", LogLevel.Error); continue; } if (!this.AddedConditionNames.Add(condition.Name)) { this.Monitor.Log($" Skipped {condition.Name}, another condition with this name already exists.", LogLevel.Error); continue; } data.Conditionals.Add(condition); } } // validate minecarts if (config.Teleporters != null) { currentStep = "Teleporters"; foreach (TeleporterList list in config.Teleporters) { bool valid = true; foreach (TeleporterList prevList in this.AddedTeleporters) { if (prevList.ListName == list.ListName) { valid = false; foreach (TeleporterDestination dest in list.Destinations) { if (prevList.Destinations.TrueForAll(a => !a.Equals(dest))) { prevList.Destinations.Add(dest); } else { this.Monitor.Log($" Can't add teleporter destination for the `{list.ListName}` teleporter, the destination already exists: `{dest}`.", LogLevel.Error); } } this.Monitor.Log($" Teleporter updated: {prevList}", LogLevel.Trace); break; } } if (valid) { this.AddedTeleporters.Add(list); this.Monitor.Log($" Teleporter created: {list}", LogLevel.Trace); } } } // validate shops if (config.Shops != null) { currentStep = "Shops"; foreach (string shop in config.Shops) { try { ShopConfig shopConfig = contentPack.ReadJsonFile <ShopConfig>($"{shop}.json"); if (shopConfig == null) { this.Monitor.Log($" Skipped shop '{shop}.json': file does not exist.", LogLevel.Error); continue; } shopConfig.Name = shop; if (!string.IsNullOrWhiteSpace(shopConfig.Portrait)) { try { shopConfig.PortraitTexture = contentPack.LoadAsset <Texture2D>(shopConfig.Portrait); } catch (Exception ex) { this.Monitor.Log($"Can't load texture '{shopConfig.Portrait}' from content pack {contentPack.Manifest.Name}.", LogLevel.Error); this.Monitor.Log(ex.ToString()); } } data.Shops.Add(shopConfig); } catch (Exception ex) { this.Monitor.Log($" Skipped shop '{shop}.json': unexpected error parsing file.", LogLevel.Error, ex); } } } } catch (Exception ex) { this.Monitor.Log($" Failed validating config (step: {currentStep}).", LogLevel.Error, ex); } return(data); }
/// <summary>Checks whether a config file should be used with the currently loaded farm.</summary> /// <param name="config">The FarmConfig to be checked.</param> /// <param name="pack">The content pack associated with this file, if any.</param> /// <returns>True if the file should be used with the current farm; false otherwise.</returns> public static bool CheckFileConditions(FarmConfig config, IContentPack pack) { Monitor.Log("Checking file conditions...", LogLevel.Trace); //check farm type if (config.File_Conditions.FarmTypes != null && config.File_Conditions.FarmTypes.Length > 0) { Monitor.Log("Farm type condition(s) found. Checking...", LogLevel.Trace); bool validType = false; foreach (object obj in config.File_Conditions.FarmTypes) //for each listed farm type { int type = -1; //parse the farm type object into an integer (int type) if (obj is long || obj is int) //if the object is a readable integer { type = Convert.ToInt32(obj); //convert it to a 32-bit integer and use it } else if (obj is string name) //if the object is a string, cast it as one { string farmTypeID = Helper.Reflection.GetMethod(typeof(Game1), "GetFarmTypeID", false)?.Invoke <string>(); //get the farm type ID string (null in SDV 1.5.4 or older) if (name.Equals("All", StringComparison.OrdinalIgnoreCase) || name.Equals("Any", StringComparison.OrdinalIgnoreCase)) //if this is "all" or "any" { validType = true; break; //skip checking the rest of the farm types } else if (name.Equals(farmTypeID, StringComparison.OrdinalIgnoreCase)) //if this is the name of the player's SDV-supported custom farm type (SDV 1.5.5 or newer) { validType = true; break; //skip checking the rest of the farm types } else if (Enum.TryParse(name, true, out FarmTypes farmType)) //if this name can be parsed into a FarmTypes enum { type = (int)farmType; //use it as an integer } else //if this is a string, but not a recognized value { if (int.TryParse(name, out int parsed)) //if this string can be parsed as an integer { type = parsed; //use the parsed value } else //if this string cannot be parsed { Monitor.Log($"This setting in the Farm Types list could not be parsed: {name}", LogLevel.Debug); Monitor.Log($"The setting will be ignored. If it's intended to be a custom farm type, please use its ID number instead of its name.", LogLevel.Debug); continue; //skip to the next farm type condition } } } if (type == Game1.whichFarm) //if the parsed type matches the current farm type { validType = true; break; //skip checking the rest of the farm types } else if (Game1.whichFarm == 200) //if this may be a MTN farm type, handle compatibility (based on MTN 2.1.0-beta8) { //if MTN is installed, use reflection to access its "whichFarm" equivalent try { IModInfo modInfo = Helper.ModRegistry.Get("SgtPickles.MTN"); // get MTN info (null if it's not installed) if (modInfo == null) //if MTN isn't installed { continue; //skip to the next farm type check } object mod = modInfo.GetType().GetProperty("Mod", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)?.GetValue(modInfo); //get MTN's Mod instance if (mod == null) //if it couldn't be accessed { continue; //skip to the next farm type check } object customManager = mod.GetType().GetProperty("CustomManager", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)?.GetValue(mod); //get CustomManager instance if (customManager == null) //if it couldn't be accessed { continue; //skip to the next farm type check } object loadedFarm = customManager.GetType().GetProperty("LoadedFarm", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)?.GetValue(customManager); //get LoadedFarm instance if (loadedFarm == null) //if it couldn't be accessed { continue; //skip to the next farm type check } int farmID = (int)loadedFarm.GetType().GetProperty("ID", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)?.GetValue(loadedFarm); //get the loaded farm's ID if (type == farmID) //if MTN's custom farm ID matches the parsed type { Monitor.VerboseLog($"Farm type matches the loaded MTN farm type's ID: {type}"); validType = true; break; //skip checking the rest of the farm types } } catch (Exception) //if any exception is thrown while accessing MTN { Monitor.Log($"Error encountered while trying to check MTN farm type. This check may be obsolete or require updates.", LogLevel.Trace); continue; //skip to the next farm type check } } } if (validType) //if a valid farm type was listed { Monitor.Log("Farm type matched a setting. File allowed.", LogLevel.Trace); } else { Monitor.Log("Farm type did NOT match any settings. File disabled.", LogLevel.Trace); return(false); //prevent config use } } //check farmer name if (config.File_Conditions.FarmerNames != null && config.File_Conditions.FarmerNames.Length > 0) { Monitor.Log("Farmer name condition(s) found. Checking...", LogLevel.Trace); bool validName = false; foreach (string name in config.File_Conditions.FarmerNames) //for each listed name { if (name.Equals(Game1.player.Name, StringComparison.OrdinalIgnoreCase)) //if the name matches the current player's { validName = true; break; //skip the rest of these checks } } if (validName) //if a valid farmer name was listed { Monitor.Log("Farmer name matched a setting. File allowed.", LogLevel.Trace); } else { Monitor.Log("Farmer name did NOT match any settings. File disabled.", LogLevel.Trace); return(false); //prevent config use } } //check save file names (technically the save folder name) if (config.File_Conditions.SaveFileNames != null && config.File_Conditions.SaveFileNames.Length > 0) { Monitor.Log("Save file name condition(s) found. Checking...", LogLevel.Trace); bool validSave = false; foreach (string saveName in config.File_Conditions.SaveFileNames) //for each listed save name { if (saveName.Equals(Constants.SaveFolderName, StringComparison.OrdinalIgnoreCase)) //if the name matches the current player's save folder name { validSave = true; break; //skip the rest of these checks } } if (validSave) //if a valid save name was listed { Monitor.Log("Save file name matched a setting. File allowed.", LogLevel.Trace); } else { Monitor.Log("Save file name did NOT match any settings. File disabled.", LogLevel.Trace); return(false); //prevent config use } } //check whether other mods exist if (config.File_Conditions.OtherMods != null && config.File_Conditions.OtherMods.Count > 0) { Monitor.Log("Other mod condition(s) found. Checking...", LogLevel.Trace); bool validMods = true; //whether all entries are accurate (true by default, unlike most other settings) foreach (KeyValuePair <string, bool> entry in config.File_Conditions.OtherMods) //for each mod entry in OtherMods { bool validEntry = !(entry.Value); //whether the current entry is accurate (starts false if the mod should exist; starts true if the mod should NOT exist) foreach (IModInfo mod in Helper.ModRegistry.GetAll()) //for each mod currently loaded by SMAPI { if (entry.Value == true) //if the mod should exist { if (entry.Key.Equals(mod.Manifest.UniqueID, StringComparison.OrdinalIgnoreCase)) //if this mod's UniqueID matches the OtherMods entry { validEntry = true; //this entry is valid break; //skip the rest of these checks } } else //if the mod should NOT exist { if (entry.Key.Equals(mod.Manifest.UniqueID, StringComparison.OrdinalIgnoreCase)) //if this mod's UniqueID matches the OtherMods entry { validEntry = false; //this entry is invalid break; //skip the rest of these checks } } } if (validEntry) //if the current mod entry is valid { Monitor.Log($"Mod check successful: \"{entry.Key}\" {(entry.Value ? "does exist" : "does not exist")}.", LogLevel.Trace); } else //if the current mod entry is NOT valid { Monitor.Log($"Mod check failed: \"{entry.Key}\" {(entry.Value ? "does not exist" : "does exist")}.", LogLevel.Trace); validMods = false; break; //skip the rest of these checks } } if (validMods) //if all mod entries in the list are valid { Monitor.Log("The OtherMods list matches the player's mods. File allowed.", LogLevel.Trace); } else //if any entries were NOT valid { Monitor.Log("The OtherMods list does NOT match the player's mods. File disabled.", LogLevel.Trace); return(false); //prevent config use } } return(true); //all checks were successful; config should be used }
private void loadData(IContentPack contentPack) { Log.info($"\t{contentPack.Manifest.Name} {contentPack.Manifest.Version} by {contentPack.Manifest.Author} - {contentPack.Manifest.Description}"); // load objects DirectoryInfo objectsDir = new DirectoryInfo(Path.Combine(contentPack.DirectoryPath, "Objects")); if (objectsDir.Exists) { foreach (DirectoryInfo dir in objectsDir.EnumerateDirectories()) { string relativePath = $"Objects/{dir.Name}"; // load data ObjectData obj = contentPack.ReadJsonFile <ObjectData>($"{relativePath}/object.json"); if (obj == null) { continue; } // save object obj.texture = contentPack.LoadAsset <Texture2D>($"{relativePath}/object.png"); if (obj.IsColored) { obj.textureColor = contentPack.LoadAsset <Texture2D>($"{relativePath}/color.png"); } this.objects.Add(obj); // save ring if (obj.Category == ObjectData.Category_.Ring) { this.myRings.Add(obj); } } } // load crops DirectoryInfo cropsDir = new DirectoryInfo(Path.Combine(contentPack.DirectoryPath, "Crops")); if (cropsDir.Exists) { foreach (DirectoryInfo dir in cropsDir.EnumerateDirectories()) { string relativePath = $"Crops/{dir.Name}"; // load data CropData crop = contentPack.ReadJsonFile <CropData>($"{relativePath}/crop.json"); if (crop == null) { continue; } // save crop crop.texture = contentPack.LoadAsset <Texture2D>($"{relativePath}/crop.png"); crops.Add(crop); // save seeds crop.seed = new ObjectData { texture = contentPack.LoadAsset <Texture2D>($"{relativePath}/seeds.png"), Name = crop.SeedName, Description = crop.SeedDescription, Category = ObjectData.Category_.Seeds, Price = crop.SeedPurchasePrice, CanPurchase = true, PurchaseFrom = crop.SeedPurchaseFrom, PurchasePrice = crop.SeedPurchasePrice, PurchaseRequirements = crop.SeedPurchaseRequirements ?? new List <string>(), NameLocalization = crop.SeedNameLocalization, DescriptionLocalization = crop.SeedDescriptionLocalization }; // TODO: Clean up this chunk // I copy/pasted it from the unofficial update decompiled string str = ""; string[] array = new[] { "spring", "summer", "fall", "winter" } .Except(crop.Seasons) .ToArray(); foreach (var season in array) { str += $"/z {season}"; } string strtrimstart = str.TrimStart(new char[] { '/' }); if (crop.SeedPurchaseRequirements != null && crop.SeedPurchaseRequirements.Count > 0) { for (int index = 0; index < crop.SeedPurchaseRequirements.Count; index++) { if (SeasonLimiter.IsMatch(crop.SeedPurchaseRequirements[index])) { crop.SeedPurchaseRequirements[index] = strtrimstart; Log.warn($" Faulty season requirements for {crop.SeedName}!\n Fixed season requirements: {crop.SeedPurchaseRequirements[index]}"); } } if (!crop.SeedPurchaseRequirements.Contains(str.TrimStart('/'))) { Log.trace($" Adding season requirements for {crop.SeedName}:\n New season requirements: {strtrimstart}"); crop.seed.PurchaseRequirements.Add(strtrimstart); } } else { Log.trace($" Adding season requirements for {crop.SeedName}:\n New season requirements: {strtrimstart}"); crop.seed.PurchaseRequirements.Add(strtrimstart); } objects.Add(crop.seed); } } // load fruit trees DirectoryInfo fruitTreesDir = new DirectoryInfo(Path.Combine(contentPack.DirectoryPath, "FruitTrees")); if (fruitTreesDir.Exists) { foreach (DirectoryInfo dir in fruitTreesDir.EnumerateDirectories()) { string relativePath = $"FruitTrees/{dir.Name}"; // load data FruitTreeData tree = contentPack.ReadJsonFile <FruitTreeData>($"{relativePath}/tree.json"); if (tree == null) { continue; } // save fruit tree tree.texture = contentPack.LoadAsset <Texture2D>($"{relativePath}/tree.png"); fruitTrees.Add(tree); // save seed tree.sapling = new ObjectData { texture = contentPack.LoadAsset <Texture2D>($"{relativePath}/sapling.png"), Name = tree.SaplingName, Description = tree.SaplingDescription, Category = ObjectData.Category_.Seeds, Price = tree.SaplingPurchasePrice, CanPurchase = true, PurchaseRequirements = tree.SaplingPurchaseRequirements, PurchaseFrom = tree.SaplingPurchaseFrom, PurchasePrice = tree.SaplingPurchasePrice, NameLocalization = tree.SaplingNameLocalization, DescriptionLocalization = tree.SaplingDescriptionLocalization }; objects.Add(tree.sapling); } } // load big craftables DirectoryInfo bigCraftablesDir = new DirectoryInfo(Path.Combine(contentPack.DirectoryPath, "BigCraftables")); if (bigCraftablesDir.Exists) { foreach (DirectoryInfo dir in bigCraftablesDir.EnumerateDirectories()) { string relativePath = $"BigCraftables/{dir.Name}"; // load data BigCraftableData craftable = contentPack.ReadJsonFile <BigCraftableData>($"{relativePath}/big-craftable.json"); if (craftable == null) { continue; } // save craftable craftable.texture = contentPack.LoadAsset <Texture2D>($"{relativePath}/big-craftable.png"); bigCraftables.Add(craftable); } } // load hats DirectoryInfo hatsDir = new DirectoryInfo(Path.Combine(contentPack.DirectoryPath, "Hats")); if (hatsDir.Exists) { foreach (DirectoryInfo dir in hatsDir.EnumerateDirectories()) { string relativePath = $"Hats/{dir.Name}"; // load data HatData hat = contentPack.ReadJsonFile <HatData>($"{relativePath}/hat.json"); if (hat == null) { continue; } // save object hat.texture = contentPack.LoadAsset <Texture2D>($"{relativePath}/hat.png"); hats.Add(hat); } } // Load weapons // load objects DirectoryInfo weaponsDir = new DirectoryInfo(Path.Combine(contentPack.DirectoryPath, "Weapons")); if (weaponsDir.Exists) { foreach (DirectoryInfo dir in weaponsDir.EnumerateDirectories()) { string relativePath = $"Weapons/{dir.Name}"; // load data WeaponData weapon = contentPack.ReadJsonFile <WeaponData>($"{relativePath}/weapon.json"); if (weapon == null) { continue; } // save object weapon.texture = contentPack.LoadAsset <Texture2D>($"{relativePath}/weapon.png"); weapons.Add(weapon); } } }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="path">The path to the patch from the root content file.</param> /// <param name="assetName">The normalized asset name to intercept.</param> /// <param name="conditions">The conditions which determine whether this patch should be applied.</param> /// <param name="fromFile">The normalized asset key from which to load entries (if applicable), including tokens.</param> /// <param name="records">The data records to edit.</param> /// <param name="fields">The data fields to edit.</param> /// <param name="moveRecords">The records to reorder, if the target is a list asset.</param> /// <param name="textOperations">The text operations to apply to existing values.</param> /// <param name="updateRate">When the patch should be updated.</param> /// <param name="contentPack">The content pack which requested the patch.</param> /// <param name="parentPatch">The parent patch for which this patch was loaded, if any.</param> /// <param name="monitor">Encapsulates monitoring and logging.</param> /// <param name="normalizeAssetName">Normalize an asset name.</param> /// <param name="tryParseFields">Parse the data change fields for an <see cref="PatchType.EditData"/> patch.</param> public EditDataPatch(LogPathBuilder path, IManagedTokenString assetName, IEnumerable <Condition> conditions, IManagedTokenString fromFile, IEnumerable <EditDataPatchRecord> records, IEnumerable <EditDataPatchField> fields, IEnumerable <EditDataPatchMoveRecord> moveRecords, IEnumerable <TextOperation> textOperations, UpdateRate updateRate, IContentPack contentPack, IPatch parentPatch, IMonitor monitor, Func <string, string> normalizeAssetName, TryParseFieldsDelegate tryParseFields) : base( path: path, type: PatchType.EditData, assetName: assetName, conditions: conditions, updateRate: updateRate, contentPack: contentPack, parentPatch: parentPatch, normalizeAssetName: normalizeAssetName, fromAsset: fromFile ) { // set fields this.Records = records?.ToArray(); this.Fields = fields?.ToArray(); this.MoveRecords = moveRecords?.ToArray(); this.TextOperations = textOperations?.ToArray() ?? new TextOperation[0]; this.Monitor = monitor; this.TryParseFields = tryParseFields; // track contextuals this.Contextuals .Add(this.Records) .Add(this.Fields) .Add(this.MoveRecords) .Add(this.TextOperations) .Add(this.Conditions); }
/// <summary>Get the local value providers with which to initialize a local context.</summary> /// <param name="contentPack">The content pack for which to get tokens.</param> private IEnumerable <IValueProvider> GetLocalValueProviders(IContentPack contentPack) { yield return(new HasFileValueProvider(contentPack.DirectoryPath)); yield return(new RandomValueProvider()); // per-pack for more reproducible selection when troubleshooting }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="indexPath">The path of indexes from the root <c>content.json</c> to this patch; see <see cref="IPatch.IndexPath"/>.</param> /// <param name="path">The path to the patch from the root content file.</param> /// <param name="assetName">The normalized asset name to intercept.</param> /// <param name="conditions">The conditions which determine whether this patch should be applied.</param> /// <param name="fromAsset">The asset key to load from the content pack instead.</param> /// <param name="fromArea">The sprite area from which to read an image.</param> /// <param name="toArea">The sprite area to overwrite.</param> /// <param name="patchMode">Indicates how the image should be patched.</param> /// <param name="updateRate">When the patch should be updated.</param> /// <param name="contentPack">The content pack which requested the patch.</param> /// <param name="parentPatch">The parent patch for which this patch was loaded, if any.</param> /// <param name="monitor">Encapsulates monitoring and logging.</param> /// <param name="normalizeAssetName">Normalize an asset name.</param> public EditImagePatch(int[] indexPath, LogPathBuilder path, IManagedTokenString assetName, IEnumerable <Condition> conditions, IManagedTokenString fromAsset, TokenRectangle fromArea, TokenRectangle toArea, PatchImageMode patchMode, UpdateRate updateRate, IContentPack contentPack, IPatch parentPatch, IMonitor monitor, Func <string, string> normalizeAssetName) : base( indexPath: indexPath, path: path, type: PatchType.EditImage, assetName: assetName, conditions: conditions, normalizeAssetName: normalizeAssetName, fromAsset: fromAsset, updateRate: updateRate, contentPack: contentPack, parentPatch: parentPatch ) { this.FromArea = fromArea; this.ToArea = toArea; this.PatchMode = patchMode; this.Monitor = monitor; this.Contextuals .Add(fromArea) .Add(toArea); }
internal static void ApplyTilesheet(IContentHelper coreContentHelper, IContentPack contentPack, Tilesheet tilesheet) { int stage = 0; int branch = 0; int skip = 0; try { stage++; // 1 GameLocation location = Game1.getLocationFromName(tilesheet.MapName); stage++; // 2 if (tilesheet.FileName == null) { skip = 1; } else { skip = 2; string fakepath = Path.Combine("AdvancedLocationLoader/FakePath_paths_objects", tilesheet.FileName); if (tilesheet.Seasonal) { fakepath = fakepath.Replace("all_sheet_paths_objects", Path.Combine("all_sheet_paths_objects", Game1.currentSeason)); } stage++; // 3 if (!_MappingCache.Contains(tilesheet.FileName)) { string toAssetPath = contentPack.GetRelativePath( fromAbsolutePath: ModEntry.SHelper.DirectoryPath, toLocalPath: tilesheet.Seasonal ? ($"{tilesheet.FileName}_{Game1.currentSeason}") : tilesheet.FileName ); coreContentHelper.RegisterXnbReplacement(fakepath, toAssetPath); _MappingCache.Add(tilesheet.FileName); } stage++; // 4 if (location.map.GetTileSheet(tilesheet.SheetId) != null) { branch = 1; location.map.GetTileSheet(tilesheet.SheetId).ImageSource = fakepath; } else { branch = 2; Texture2D sheet = Game1.content.Load <Texture2D>(fakepath); location.map.AddTileSheet(new xTile.Tiles.TileSheet(tilesheet.SheetId, location.map, fakepath, new xTile.Dimensions.Size((int)Math.Ceiling(sheet.Width / 16.0), (int)Math.Ceiling(sheet.Height / 16.0)), new xTile.Dimensions.Size(16, 16))); } } stage++; // 5 (skip 3) if (tilesheet.Properties.Count > 0) { xTile.Tiles.TileSheet sheet = location.map.GetTileSheet(tilesheet.SheetId); foreach (string prop in tilesheet.Properties.Keys) { sheet.Properties[prop] = tilesheet.Properties[prop]; } } } catch (Exception err) { ModEntry.Logger.ExitGameImmediately($"Unable to patch tilesheet, a unexpected error occured at stage {stage}{(skip > 0 ? ("-" + skip) : "")}{(branch > 0 ? ("-" + branch) : "")}: {tilesheet}", err); } }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="indexPath">The path of indexes from the root <c>content.json</c> to this patch; see <see cref="IPatch.IndexPath"/>.</param> /// <param name="path">The path to the patch from the root content file.</param> /// <param name="assetName">The normalized asset name to intercept.</param> /// <param name="conditions">The conditions which determine whether this patch should be applied.</param> /// <param name="fromAsset">The asset key to load from the content pack instead.</param> /// <param name="fromArea">The map area from which to read tiles.</param> /// <param name="patchMode">Indicates how the map should be patched.</param> /// <param name="toArea">The map area to overwrite.</param> /// <param name="mapProperties">The map properties to change when editing a map, if any.</param> /// <param name="mapTiles">The map tiles to change when editing a map.</param> /// <param name="addWarps">The warps to add to the location.</param> /// <param name="textOperations">The text operations to apply to existing values.</param> /// <param name="updateRate">When the patch should be updated.</param> /// <param name="contentPack">The content pack which requested the patch.</param> /// <param name="parentPatch">The parent patch for which this patch was loaded, if any.</param> /// <param name="monitor">Encapsulates monitoring and logging.</param> /// <param name="reflection">Simplifies access to private code.</param> /// <param name="normalizeAssetName">Normalize an asset name.</param> public EditMapPatch(int[] indexPath, LogPathBuilder path, IManagedTokenString assetName, IEnumerable <Condition> conditions, IManagedTokenString fromAsset, TokenRectangle fromArea, TokenRectangle toArea, PatchMapMode patchMode, IEnumerable <EditMapPatchProperty> mapProperties, IEnumerable <EditMapPatchTile> mapTiles, IEnumerable <IManagedTokenString> addWarps, IEnumerable <TextOperation> textOperations, UpdateRate updateRate, IContentPack contentPack, IPatch parentPatch, IMonitor monitor, IReflectionHelper reflection, Func <string, string> normalizeAssetName) : base( indexPath: indexPath, path: path, type: PatchType.EditMap, assetName: assetName, fromAsset: fromAsset, conditions: conditions, updateRate: updateRate, contentPack: contentPack, parentPatch: parentPatch, normalizeAssetName: normalizeAssetName ) { this.FromArea = fromArea; this.ToArea = toArea; this.PatchMode = patchMode; this.MapProperties = mapProperties?.ToArray() ?? new EditMapPatchProperty[0]; this.MapTiles = mapTiles?.ToArray() ?? new EditMapPatchTile[0]; this.AddWarps = addWarps?.Reverse().ToArray() ?? new IManagedTokenString[0]; // reversing the warps allows later ones to 'overwrite' earlier ones, since the game checks them in the listed order this.TextOperations = textOperations?.ToArray() ?? new TextOperation[0]; this.Monitor = monitor; this.Reflection = reflection; this.Contextuals .Add(this.FromArea) .Add(this.ToArea) .Add(this.MapProperties) .Add(this.MapTiles) .Add(this.AddWarps) .Add(this.TextOperations); }
/// <summary>Checks whether a config file should be used with the currently loaded farm.</summary> /// <param name="config">The FarmConfig to be checked.</param> /// <param name="pack">The content pack associated with this file, if any.</param> /// <returns>True if the file should be used with the current farm; false otherwise.</returns> public static bool CheckFileConditions(FarmConfig config, IContentPack pack) { Monitor.Log("Checking file conditions...", LogLevel.Trace); //check "reset main data folder" flag //NOTE: it's preferable to do this as the first step; it's intended to be a one-off cleaning process, rather than a conditional effect if (config.File_Conditions.ResetMainDataFolder && MConfig.EnableContentPackFileChanges) //if "reset" is true and file changes are enabled { if (pack != null) //if this is part of a content pack { //attempt to load the content pack's global save data ContentPackSaveData packSave = null; try { packSave = pack.ReadJsonFile <ContentPackSaveData>(Path.Combine("data", "ContentPackSaveData.save")); //load the content pack's global save data (null if it doesn't exist) } catch (Exception ex) { Monitor.Log($"Warning: This content pack's save data could not be parsed correctly: {pack.Manifest.Name}", LogLevel.Warn); Monitor.Log($"Affected file: data/ContentPackSaveData.save", LogLevel.Warn); Monitor.Log($"Please delete the file and/or contact the mod's developer.", LogLevel.Warn); Monitor.Log($"The content pack will be skipped until this issue is fixed. The auto-generated error message is displayed below:", LogLevel.Warn); Monitor.Log($"----------", LogLevel.Warn); Monitor.Log($"{ex.Message}", LogLevel.Warn); return(false); //disable this content pack's config, since it may require this process to function } if (packSave == null) //no global save data exists for this content pack { packSave = new ContentPackSaveData(); } if (!packSave.MainDataFolderReset) //if this content pack has NOT reset the main data folder yet { Monitor.Log($"ResetMainDataFolder requested by content pack: {pack.Manifest.Name}", LogLevel.Debug); string dataPath = Path.Combine(Helper.DirectoryPath, "data"); //the path to this mod's data folder DirectoryInfo dataFolder = new DirectoryInfo(dataPath); //an object representing this mod's data directory if (dataFolder.Exists) //the data folder exists { Monitor.Log("Attempting to archive data folder...", LogLevel.Trace); try { string archivePath = Path.Combine(Helper.DirectoryPath, "data", "archive", DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss")); DirectoryInfo archiveFolder = Directory.CreateDirectory(archivePath); //create a timestamped archive folder foreach (FileInfo file in dataFolder.GetFiles()) //for each file in dataFolder { file.MoveTo(Path.Combine(archiveFolder.FullName, file.Name)); //move each file to archiveFolder } } catch (Exception ex) { Monitor.Log($"Warning: This content pack attempted to archive Farm Type Manager's data folder but failed: {pack.Manifest.Name}", LogLevel.Warn); Monitor.Log($"Please report this issue to Farm Type Manager's developer. This might also be fixed by manually removing your FarmTypeManager/data/ files.", LogLevel.Warn); Monitor.Log($"The content pack will be skipped until this issue is fixed. The auto-generated error message is displayed below:", LogLevel.Warn); Monitor.Log($"----------", LogLevel.Warn); Monitor.Log($"{ex.Message}", LogLevel.Warn); return(false); //disable this content pack's config, since it may require this process to function } } else //the data folder doesn't exist { Monitor.Log("Data folder not found; assuming it was deleted or not yet generated.", LogLevel.Trace); } packSave.MainDataFolderReset = true; //update save data } pack.WriteJsonFile(Path.Combine("data", "ContentPackSaveData.save"), packSave); //update the content pack's global save data file Monitor.Log("Data folder archive successful.", LogLevel.Trace); } else //if this is NOT part of a content pack { Monitor.Log("This farm's config file has ResetMainDataFolder = true, but this setting only works for content packs.", LogLevel.Info); } } //check farm type if (config.File_Conditions.FarmTypes != null && config.File_Conditions.FarmTypes.Length > 0) { Monitor.Log("Farm type condition(s) found. Checking...", LogLevel.Trace); bool validType = false; foreach (object obj in config.File_Conditions.FarmTypes) //for each listed farm type { int type = -1; //parse the farm type object into an integer (int type) if (obj is long || obj is int) //if the object is a readable integer { type = Convert.ToInt32(obj); //convert it to a 32-bit integer and use it } else if (obj is string name) //if the object is a string, cast it as one { if (name.Equals("All", StringComparison.OrdinalIgnoreCase) || name.Equals("Any", StringComparison.OrdinalIgnoreCase)) //if this is "all" or "any" { validType = true; break; //skip checking the rest of the farm types } else if (Enum.TryParse(name, true, out FarmTypes farmType)) //if this name can be parsed into a FarmTypes enum { type = (int)farmType; //use it as an integer } else //if this is a string, but not a recognized value { if (int.TryParse(name, out int parsed)) //if this string can be parsed as an integer { type = parsed; //use the parsed value } else //if this string cannot be parsed { Monitor.Log($"This setting in the Farm Types list could not be parsed: {name}", LogLevel.Debug); Monitor.Log($"The setting will be ignored. If it's intended to be a custom farm type, please use its ID number instead of its name.", LogLevel.Debug); continue; //skip to the next farm type condition } } } if (type == Game1.whichFarm) //if the parsed type matches the current farm type { validType = true; break; //skip checking the rest of the farm types } else if (Game1.whichFarm == 200) //if this may be a MTN farm type, handle compatibility (based on MTN 2.1.0-beta8) { //if MTN is installed, use reflection to access its "whichFarm" equivalent try { IModInfo modInfo = Helper.ModRegistry.Get("SgtPickles.MTN"); // get MTN info (null if it's not installed) if (modInfo == null) //if MTN isn't installed { continue; //skip to the next farm type check } object mod = modInfo.GetType().GetProperty("Mod", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)?.GetValue(modInfo); //get MTN's Mod instance if (mod == null) //if it couldn't be accessed { continue; //skip to the next farm type check } object customManager = mod.GetType().GetProperty("CustomManager", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)?.GetValue(mod); //get CustomManager instance if (customManager == null) //if it couldn't be accessed { continue; //skip to the next farm type check } object loadedFarm = customManager.GetType().GetProperty("LoadedFarm", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)?.GetValue(customManager); //get LoadedFarm instance if (loadedFarm == null) //if it couldn't be accessed { continue; //skip to the next farm type check } int farmID = (int)loadedFarm.GetType().GetProperty("ID", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)?.GetValue(loadedFarm); //get the loaded farm's ID if (type == farmID) //if MTN's custom farm ID matches the parsed type { Monitor.VerboseLog($"Farm type matches the loaded MTN farm type's ID: {type}"); validType = true; break; //skip checking the rest of the farm types } } catch (Exception) //if any exception is thrown while accessing MTN { Monitor.Log($"Error encountered while trying to check MTN farm type. This check may be obsolete or require updates.", LogLevel.Trace); continue; //skip to the next farm type check } } } if (validType) //if a valid farm type was listed { Monitor.Log("Farm type matched a setting. File allowed.", LogLevel.Trace); } else { Monitor.Log("Farm type did NOT match any settings. File disabled.", LogLevel.Trace); return(false); //prevent config use } } //check farmer name if (config.File_Conditions.FarmerNames != null && config.File_Conditions.FarmerNames.Length > 0) { Monitor.Log("Farmer name condition(s) found. Checking...", LogLevel.Trace); bool validName = false; foreach (string name in config.File_Conditions.FarmerNames) //for each listed name { if (name.Equals(Game1.player.Name, StringComparison.OrdinalIgnoreCase)) //if the name matches the current player's { validName = true; break; //skip the rest of these checks } } if (validName) //if a valid farmer name was listed { Monitor.Log("Farmer name matched a setting. File allowed.", LogLevel.Trace); } else { Monitor.Log("Farmer name did NOT match any settings. File disabled.", LogLevel.Trace); return(false); //prevent config use } } //check save file names (technically the save folder name) if (config.File_Conditions.SaveFileNames != null && config.File_Conditions.SaveFileNames.Length > 0) { Monitor.Log("Save file name condition(s) found. Checking...", LogLevel.Trace); bool validSave = false; foreach (string saveName in config.File_Conditions.SaveFileNames) //for each listed save name { if (saveName.Equals(Constants.SaveFolderName, StringComparison.OrdinalIgnoreCase)) //if the name matches the current player's save folder name { validSave = true; break; //skip the rest of these checks } } if (validSave) //if a valid save name was listed { Monitor.Log("Save file name matched a setting. File allowed.", LogLevel.Trace); } else { Monitor.Log("Save file name did NOT match any settings. File disabled.", LogLevel.Trace); return(false); //prevent config use } } //check whether other mods exist if (config.File_Conditions.OtherMods != null && config.File_Conditions.OtherMods.Count > 0) { Monitor.Log("Other mod condition(s) found. Checking...", LogLevel.Trace); bool validMods = true; //whether all entries are accurate (true by default, unlike most other settings) foreach (KeyValuePair <string, bool> entry in config.File_Conditions.OtherMods) //for each mod entry in OtherMods { bool validEntry = !(entry.Value); //whether the current entry is accurate (starts false if the mod should exist; starts true if the mod should NOT exist) foreach (IModInfo mod in Helper.ModRegistry.GetAll()) //for each mod currently loaded by SMAPI { if (entry.Value == true) //if the mod should exist { if (entry.Key.Equals(mod.Manifest.UniqueID, StringComparison.OrdinalIgnoreCase)) //if this mod's UniqueID matches the OtherMods entry { validEntry = true; //this entry is valid break; //skip the rest of these checks } } else //if the mod should NOT exist { if (entry.Key.Equals(mod.Manifest.UniqueID, StringComparison.OrdinalIgnoreCase)) //if this mod's UniqueID matches the OtherMods entry { validEntry = false; //this entry is invalid break; //skip the rest of these checks } } } if (validEntry) //if the current mod entry is valid { Monitor.Log($"Mod check successful: \"{entry.Key}\" {(entry.Value ? "does exist" : "does not exist")}.", LogLevel.Trace); } else //if the current mod entry is NOT valid { Monitor.Log($"Mod check failed: \"{entry.Key}\" {(entry.Value ? "does not exist" : "does exist")}.", LogLevel.Trace); validMods = false; break; //skip the rest of these checks } } if (validMods) //if all mod entries in the list are valid { Monitor.Log("The OtherMods list matches the player's mods. File allowed.", LogLevel.Trace); } else //if any entries were NOT valid { Monitor.Log("The OtherMods list does NOT match the player's mods. File disabled.", LogLevel.Trace); return(false); //prevent config use } } return(true); //all checks were successful; config should be used }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="contentPack">The underlying content pack.</param> public MusicPack(IContentPack contentPack) { this.ContentPack = contentPack; this.playingSounds = new Dictionary <string, List <SoundEffectInstance> >(); this.LoadMusicFiles(); }
/********* ** Private methods *********/ /// <summary>Queues an error.</summary> /// <param name="contentPack">The content pack the error is for.</param> /// <param name="id">An id to use when displaying the error.</param> /// <param name="info">The error info.</param> /// <param name="forIndividualDoor">Whether the error is for one door or an entire image.</param> private void QueueError(IContentPack contentPack, string id, string info, bool forIndividualDoor) { this.errorQueue.AddError($"{contentPack.Manifest.Name} ({contentPack.Manifest.UniqueID}) - {id} - Found an error. Info: {info}. {(forIndividualDoor ? "This door" : "Every door in this image")} won't be loaded."); }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="indexPath">The path of indexes from the root <c>content.json</c> to this patch; see <see cref="IPatch.IndexPath"/>.</param> /// <param name="path">The path to the patch from the root content file.</param> /// <param name="assetName">The normalized asset name to intercept.</param> /// <param name="conditions">The conditions which determine whether this patch should be applied.</param> /// <param name="fromAsset">The asset key to load from the content pack instead.</param> /// <param name="fromArea">The map area from which to read tiles.</param> /// <param name="patchMode">Indicates how the map should be patched.</param> /// <param name="toArea">The map area to overwrite.</param> /// <param name="mapProperties">The map properties to change when editing a map, if any.</param> /// <param name="mapTiles">The map tiles to change when editing a map.</param> /// <param name="addWarps">The warps to add to the location.</param> /// <param name="textOperations">The text operations to apply to existing values.</param> /// <param name="updateRate">When the patch should be updated.</param> /// <param name="contentPack">The content pack which requested the patch.</param> /// <param name="parentPatch">The parent patch for which this patch was loaded, if any.</param> /// <param name="monitor">Encapsulates monitoring and logging.</param> /// <param name="parseAssetName">Parse an asset name.</param> public EditMapPatch(int[] indexPath, LogPathBuilder path, IManagedTokenString assetName, IEnumerable <Condition> conditions, IManagedTokenString?fromAsset, TokenRectangle?fromArea, TokenRectangle?toArea, PatchMapMode patchMode, IEnumerable <EditMapPatchProperty>?mapProperties, IEnumerable <EditMapPatchTile>?mapTiles, IEnumerable <IManagedTokenString>?addWarps, IEnumerable <TextOperation>?textOperations, UpdateRate updateRate, IContentPack contentPack, IPatch?parentPatch, IMonitor monitor, Func <string, IAssetName> parseAssetName) : base( indexPath: indexPath, path: path, type: PatchType.EditMap, assetName: assetName, fromAsset: fromAsset, conditions: conditions, updateRate: updateRate, contentPack: contentPack, parentPatch: parentPatch, parseAssetName: parseAssetName ) { this.FromArea = fromArea; this.ToArea = toArea; this.PatchMode = patchMode; this.MapProperties = mapProperties?.ToArray() ?? Array.Empty <EditMapPatchProperty>(); this.MapTiles = mapTiles?.ToArray() ?? Array.Empty <EditMapPatchTile>(); this.AddWarps = addWarps?.Reverse().ToArray() ?? Array.Empty <IManagedTokenString>(); // reversing the warps allows later ones to 'overwrite' earlier ones, since the game checks them in the listed order this.TextOperations = textOperations?.ToArray() ?? Array.Empty <TextOperation>(); this.Monitor = monitor; this.Contextuals .Add(this.FromArea) .Add(this.ToArea) .Add(this.MapProperties) .Add(this.MapTiles) .Add(this.AddWarps) .Add(this.TextOperations); }
/// <summary>Construct an instance.</summary> /// <param name="pack">The content pack to manage.</param> public ManagedContentPack(IContentPack pack, IMonitor monitor, bool paranoid = false) { this.Pack = pack; this.Monitor = monitor; this.legacyDataProvider = new LegacyDataProvider(this, paranoid); }
private IEnumerable <IContentPack> GetAllContentPacks() { // read SMAPI content packs foreach (IContentPack contentPack in this.Helper.ContentPacks.GetOwned()) { yield return(contentPack); } // read legacy content packs string baseDir = Path.Combine(this.Helper.DirectoryPath, "locations"); Directory.CreateDirectory(baseDir); foreach (string dir in Directory.EnumerateDirectories(baseDir)) { IContentPack contentPack = null; try { // skip SMAPI content pack (shouldn't be installed here) if (File.Exists(Path.Combine(dir, "locations.json"))) { ModEntry.Logger.Log($"The folder at path '{dir}' looks like a SMAPI content pack. Those should be installed directly in your Mods folder. This content pack won't be loaded.", LogLevel.Warn); continue; } // read manifest string file = Path.Combine(dir, "manifest.json"); if (!File.Exists(file)) { ModEntry.Logger.Log($"Can't find a manifest.json in the '{dir}' folder. This content pack won't be loaded.", LogLevel.Warn); continue; } JObject config; try { config = (JObject)JsonConvert.DeserializeObject(File.ReadAllText(file)); } catch (Exception ex) { ModEntry.Logger.Log($"Can't read manifest.json in the '{dir}' folder. This content pack won't be loaded.", LogLevel.Error, ex); continue; } // get 'about' field JObject about = config.GetValue("About", StringComparison.InvariantCultureIgnoreCase) as JObject; if (about == null) { ModEntry.Logger.Log($"Can't read content pack 'about' info from the manifest.json in the '{dir}' folder. This content pack won't be loaded.", LogLevel.Error); continue; } // prepare basic data string id = about.GetValue("ModID", StringComparison.InvariantCultureIgnoreCase)?.Value <string>() ?? Guid.NewGuid().ToString("N"); string name = about.GetValue("ModName", StringComparison.InvariantCultureIgnoreCase)?.Value <string>() ?? Path.GetDirectoryName(dir); string author = about.GetValue("Author", StringComparison.InvariantCultureIgnoreCase)?.Value <string>(); string description = about.GetValue("Description", StringComparison.InvariantCultureIgnoreCase)?.Value <string>(); string version = about.GetValue("Version", StringComparison.InvariantCultureIgnoreCase)?.Value <string>() ?? "1.0.0"; // create content pack contentPack = this.Helper.ContentPacks.CreateTemporary(dir, id, name, description, author, new SemanticVersion(version)); } catch (Exception ex) { ModEntry.Logger.Log($"Could not parse location mod at path '{dir}'. This content pack won't be loaded.", LogLevel.Error, ex); } if (contentPack != null) { yield return(contentPack); } } }
/// <summary> /// Saves each shop as long as its has a unique name /// </summary> /// <param name="data"></param> /// <param name="contentPack"></param> public static void RegisterShops(ContentPack data, IContentPack contentPack) { ItemsUtil.RegisterPacksToRemove(data.RemovePacksFromVanilla, data.RemovePackRecipesFromVanilla, data.RemoveItemsFromVanilla); if (data.Shops != null) { foreach (ItemShop shopPack in data.Shops) { if (ItemShops.ContainsKey(shopPack.ShopName)) { ModEntry.monitor.Log($"{contentPack.Manifest.Name} is trying to add a Shop \"{shopPack.ShopName}\"," + $" but a shop of this name has already been added. " + $"It will not be added.", LogLevel.Warn); continue; } shopPack.ContentPack = contentPack; ItemShops.Add(shopPack.ShopName, shopPack); } } if (data.AnimalShops != null) { foreach (AnimalShop animalShopPack in data.AnimalShops) { if (AnimalShops.ContainsKey(animalShopPack.ShopName)) { ModEntry.monitor.Log($"{contentPack.Manifest.Name} is trying to add an AnimalShop \"{animalShopPack.ShopName}\"," + $" but a shop of this name has already been added. " + $"It will not be added.", LogLevel.Warn); continue; } AnimalShops.Add(animalShopPack.ShopName, animalShopPack); } } if (data.VanillaShops != null) { foreach (var vanillaShopPack in data.VanillaShops) { if (!VanillaShopNames.Contains(vanillaShopPack.ShopName)) { ModEntry.monitor.Log($"{contentPack.Manifest.Name}" + $" is trying to edit nonexistent vanilla store" + $" \"{vanillaShopPack.ShopName}\"", LogLevel.Warn); continue; } if (VanillaShops.ContainsKey(vanillaShopPack.ShopName)) { VanillaShops[vanillaShopPack.ShopName].StockManagers.Add(new ItemPriceAndStockManager(vanillaShopPack)); if (vanillaShopPack.ReplaceInsteadOfAdd) { VanillaShops[vanillaShopPack.ShopName].ReplaceInsteadOfAdd = true; } if (vanillaShopPack.AddStockAboveVanilla) { VanillaShops[vanillaShopPack.ShopName].AddStockAboveVanilla = true; } } else { vanillaShopPack.Initialize(); vanillaShopPack.StockManagers.Add(new ItemPriceAndStockManager(vanillaShopPack)); VanillaShops.Add(vanillaShopPack.ShopName, vanillaShopPack); } } } }
/// <summary>Constructs an instance.</summary> /// <param name="internalAnimalName">The internal name of the animal the sprite sheet is for.</param> /// <param name="internalAnimalSubtypeName">The internal name of the subtype the sprite sheet is for.</param> /// <param name="isBaby">Whether the sprite sheet is for the baby version of the animal.</param> /// <param name="isHarvested">Whether the sprite sheet is for the harvested version of the animal.</param> /// <param name="season">The season the sprite sheet is for of the animal.</param> /// <param name="relativeTexturePath">The path to the sprite sheet relative to <paramref name="contentPackAssetOwner"/>.</param> /// <param name="contentPackAssetOwner">The content pack that owns the asset.</param> public ManagedAsset(string internalAnimalName, string internalAnimalSubtypeName, bool isBaby, bool isHarvested, string season, string relativeTexturePath, IContentPack contentPackAssetOwner = null) { InternalAnimalName = internalAnimalName; InternalAnimalSubtypeName = internalAnimalSubtypeName; IsBaby = isBaby; IsHarvested = isHarvested; Season = season; RelativeTexturePath = relativeTexturePath; ContentPackAssetOwner = contentPackAssetOwner; IsGameContent = ContentPackAssetOwner == null; }
/********* ** Public methods *********/ /// <summary>Get whether a file exists in the content pack.</summary> /// <param name="contentPack">The content pack.</param> /// <param name="key">The asset key.</param> public bool FileExists(IContentPack contentPack, string key) { return(this.GetRealPath(contentPack, key) != null); }
public static Map Load(string path, IModHelper helper, IContentPack contentPack = null) { return(Load(path, helper, false, contentPack)); }
public BuildableEdit(string id, string indoorsMap, string iconFile, int price, Texture2D icon, string mapName, string location, IContentPack pack) { this.id = id; this.indoorsFile = indoorsMap; this.iconFile = iconFile; this.price = price; this._icon = icon; this._mapName = mapName; this._location = location; }
public static Map Load(string path, IModHelper helper, bool syncTexturesToClients, IContentPack contentPack) { Dictionary <TileSheet, Texture2D> tilesheets = Helper.Reflection.GetField <Dictionary <TileSheet, Texture2D> >(Game1.mapDisplayDevice, "m_tileSheetTextures").GetValue(); Map map = tmx2map(Path.Combine(contentPack != null ? contentPack.DirectoryPath : helper.DirectoryPath, path)); string fileName = new FileInfo(path).Name; foreach (TileSheet t in map.TileSheets) { string[] seasons = new string[] { "summer_", "fall_", "winter_" }; string tileSheetPath = path.Replace(fileName, t.ImageSource + ".png"); FileInfo tileSheetFile = new FileInfo(Path.Combine(contentPack != null ? contentPack.DirectoryPath : helper.DirectoryPath, tileSheetPath)); FileInfo tileSheetFileVanilla = new FileInfo(Path.Combine(PyUtils.getContentFolder(), "Content", t.ImageSource + ".xnb")); if (tileSheetFile.Exists && !tileSheetFileVanilla.Exists && tilesheets.Find(k => k.Key.ImageSource == t.ImageSource).Key == null) { Texture2D tilesheet = contentPack != null?contentPack.LoadAsset <Texture2D>(tileSheetPath) : helper.Content.Load <Texture2D>(tileSheetPath); tilesheet.inject(t.ImageSource); if (syncTexturesToClients && Game1.IsMultiplayer && Game1.IsServer) { foreach (Farmer farmhand in Game1.otherFarmers.Values) { PyNet.sendGameContent(t.ImageSource, tilesheet, farmhand, (b) => Monitor.Log("Syncing " + t.ImageSource + " to " + farmhand.Name + ": " + (b ? "successful" : "failed"), b ? LogLevel.Info : LogLevel.Warn)); } } if (t.ImageSource.Contains("spring_")) { foreach (string season in seasons) { string seasonPath = path.Replace(fileName, t.ImageSource.Replace("spring_", season)); FileInfo seasonFile = new FileInfo(Path.Combine(contentPack != null ? contentPack.DirectoryPath : helper.DirectoryPath, seasonPath + ".png")); if (seasonFile.Exists && tilesheets.Find(k => k.Key.ImageSource == t.ImageSource.Replace("spring_", season)).Key == null) { Texture2D seasonTilesheet = contentPack != null?contentPack.LoadAsset <Texture2D>(seasonPath + ".png") : helper.Content.Load <Texture2D>(seasonPath + ".png"); string seasonTextureName = t.ImageSource.Replace("spring_", season); seasonTilesheet.inject(seasonTextureName); seasonTilesheet.inject("Maps/" + seasonTextureName); if (syncTexturesToClients && Game1.IsMultiplayer && Game1.IsServer) { foreach (Farmer farmhand in Game1.otherFarmers.Values) { PyNet.sendGameContent(new string[] { seasonTextureName, "Maps/" + seasonTextureName }, seasonTilesheet, farmhand, (b) => Monitor.Log("Syncing " + seasonTextureName + " to " + farmhand.Name + ": " + (b ? "successful" : "failed"), b ? LogLevel.Info : LogLevel.Warn)); } } } } } } } map.LoadTileSheets(Game1.mapDisplayDevice); return(map); }
/// <summary>Load data from a version 1.1 manifest.</summary> /// <param name="contentPack">The content pack to load.</param> /// <param name="configPath">The content pack's relative config file path.</param> private LocationConfig ReadConfig_1_1(IContentPack contentPack, string configPath) { // read raw data LocationConfig_1_1 raw; try { raw = contentPack.ReadJsonFile <LocationConfig_1_1>(configPath); } catch (Exception err) { this.Monitor.Log(" Skipped: can't parse config file (version 1.1).", LogLevel.Warn, err); return(null); } // parse LocationConfig config = new LocationConfig(); { // convert locations if (raw.Locations != null) { foreach (IDictionary <string, string> location in raw.Locations) { config.Locations.Add(new Location { Farmable = location.ContainsKey("farmable") && Convert.ToBoolean(location["farmable"]), Outdoor = location.ContainsKey("outdoor") && Convert.ToBoolean(location["outdoor"]), FileName = location["file"], MapName = location["name"], Type = "Default" }); } } // convert overrides if (raw.Overrides != null) { foreach (IDictionary <string, string> @override in raw.Overrides) { config.Overrides.Add(new Override { FileName = @override["file"], MapName = @override["name"] }); } } // convert tilesheets if (raw.Tilesheets != null) { foreach (IDictionary <string, string> sheet in raw.Tilesheets) { config.Tilesheets.Add(new Tilesheet { FileName = sheet["file"], MapName = sheet["map"], SheetId = sheet["sheet"], Seasonal = sheet.ContainsKey("seasonal") && Convert.ToBoolean(sheet["seasonal"]) }); } } // convert tiles if (raw.Tiles != null) { foreach (IDictionary <string, string> tile in raw.Tiles) { Tile newTile = new Tile { TileX = Convert.ToInt32(tile["x"]), TileY = Convert.ToInt32(tile["y"]), MapName = tile["map"], LayerId = tile["layer"], SheetId = tile.ContainsKey("sheet") ? tile["sheet"] : null }; if (tile.ContainsKey("interval")) { newTile.Interval = Convert.ToInt32(tile["interval"]); newTile.TileIndexes = tile["tileIndex"].Split(',').Select(p => Convert.ToInt32(p)).ToArray(); } else { newTile.TileIndex = Convert.ToInt32(tile["tileIndex"]); } newTile.Conditions = tile.ContainsKey("conditions") ? tile["conditions"] : null; config.Tiles.Add(newTile); } } // convert properties if (raw.Properties != null) { foreach (IList <string> property in raw.Properties) { config.Properties.Add(new Property { MapName = property[0], LayerId = property[1], TileX = Convert.ToInt32(property[2]), TileY = Convert.ToInt32(property[3]), Key = property[4], Value = property[5] }); } } // convert warps if (raw.Warps != null) { foreach (IList <string> warp in raw.Warps) { config.Warps.Add(new Warp { MapName = warp[0], TileX = Convert.ToInt32(warp[1]), TileY = Convert.ToInt32(warp[2]), TargetName = warp[3], TargetX = Convert.ToInt32(warp[4]), TargetY = Convert.ToInt32(warp[5]) }); } } // convert conditions if (raw.Conditions != null) { foreach (KeyValuePair <string, IDictionary <string, string> > condition in raw.Conditions) { config.Conditionals.Add(new Conditional { Name = condition.Key, Item = Convert.ToInt32(condition.Value["item"]), Amount = Convert.ToInt32(condition.Value["amount"]), Question = condition.Value["question"] }); } } // convert minecarts if (raw.Minecarts != null) { foreach (KeyValuePair <string, IDictionary <string, IList <string> > > set in raw.Minecarts) { TeleporterList newSet = new TeleporterList { ListName = set.Key }; foreach (KeyValuePair <string, IList <string> > destination in set.Value) { newSet.Destinations.Add(new TeleporterDestination { ItemText = destination.Key, MapName = destination.Value[0], TileX = Convert.ToInt32(destination.Value[1]), TileY = Convert.ToInt32(destination.Value[2]), Direction = Convert.ToInt32(destination.Value[3]) }); } config.Teleporters.Add(newSet); } } // convert shops config.Shops = raw.Shops; } return(config); }
/********* ** Public methods *********/ /// <summary>The mod entry point, called after the mod is first loaded.</summary> /// <param name="helper">Provides simplified APIs for writing mods.</param> public override void Entry(IModHelper helper) { helper.Events.GameLoop.DayStarted += this.OnDayStarted; helper.Events.GameLoop.UpdateTicked += this.UpdateTicked; // collect data models IList <ThingsToForget> models = new List <ThingsToForget>(); foreach (IModInfo mod in this.Helper.ModRegistry.GetAll()) { // make sure it's a Content Patcher pack if (!mod.IsContentPack || mod.Manifest.ContentPackFor?.UniqueID.Trim().Equals("Pathoschild.ContentPatcher", StringComparison.InvariantCultureIgnoreCase) != true) { continue; } // get the directory path containing the manifest.json // HACK: IModInfo is implemented by ModMetadata, an internal SMAPI class which // contains much more non-public information. Caveats: // - This isn't part of the public API so it may break in future versions. // - Since the class is internal, we need reflection to access the values. // - SMAPI's reflection API doesn't let us reflect into SMAPI, so we need manual // reflection instead. // - SMAPI's data API doesn't let us access an absolute path, so we need to parse // the model ourselves. string directoryPath = (string)mod.GetType().GetProperty("DirectoryPath")?.GetValue(mod); if (directoryPath == null) { throw new InvalidOperationException($"Couldn't fetch the DirectoryPath property from the mod info for {mod.Manifest.Name}."); } // read the JSON file IContentPack contentPack = this.Helper.ContentPacks.CreateFake(directoryPath); models.Add(contentPack.ReadJsonFile <ThingsToForget>("content.json")); // extract event IDs foreach (ThingsToForget model in models) { if (model?.RepeatEvents == null) { continue; } foreach (int eventID in model.RepeatEvents) { this.EventsToForget.Add(eventID); } } foreach (ThingsToForget model in models) { if (model?.RepeatMail == null) { continue; } foreach (string mailID in model.RepeatMail) { this.MailToForget.Add(mailID); } } foreach (ThingsToForget model in models) { if (model?.RepeatResponse == null) { continue; } foreach (int ResponseID in model.RepeatResponse) { this.ResponseToForget.Add(ResponseID); } } } helper.ConsoleCommands.Add("eventforget", "'usage: eventforget <id>", ForgetManualCommand); helper.ConsoleCommands.Add("showevents", "'usage: Lists all completed events", ShowEventsCommand); helper.ConsoleCommands.Add("showmail", "'usage: Lists all seen mail", ShowMailCommand); helper.ConsoleCommands.Add("mailforget", "'usage: mailforget <id>", ForgetMailCommand); helper.ConsoleCommands.Add("sendme", "'usage: sendme <id>", SendMailCommand); helper.ConsoleCommands.Add("showresponse", "'usage: Lists Response IDs. For ADVANCED USERS!!", ShowResponseCommand); helper.ConsoleCommands.Add("responseforget", "'usage: responseforget <id>'", ForgetResponseCommand); //helper.ConsoleCommands.Add("responseadd", "'usage: responseadd <id>' Inject a question response.", ResponseAddCommand); helper.ConsoleCommands.Add("repeateradd", "'usage: repeateradd <id(optional)>' Create a repeatable event. If no id is given, the last seen will be repeated. Works on Next Day", ManualRepeater); helper.ConsoleCommands.Add("repeatersave", "'usage: repeatersave <filename>' Creates a textfile with all events you set to repeat manually.", SaveManualCommand); helper.ConsoleCommands.Add("repeaterload", "'usage: repeaterload <filename>' Loads the file you designate.", LoadCommand); helper.ConsoleCommands.Add("inject", "'usage: inject <event, mail, response> <ID>' Example: 'inject event 1324329' Inject IDs into the game.", injectCommand); }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="pack">The content pack to manage.</param> public ManagedContentPack(IContentPack pack) { this.Pack = pack; }
/// <summary>Validates a single instance of farm data, correcting obsolete/invalid settings automatically.</summary> /// <param name="config">The contents of a single config file to be validated.</param> /// <param name="pack">The content pack associated with this config data; null if the file was from this mod's own data folder.</param> public static void ValidateFarmData(FarmConfig config, IContentPack pack) { if (pack != null) { Monitor.Log($"Validating data from content pack: {pack.Manifest.Name}", LogLevel.Trace); } else { Monitor.Log("Validating data from FarmTypeManager/data", LogLevel.Trace); } List <SpawnArea[]> allAreas = new List <SpawnArea[]>(); //a unified list of each "Areas" array in this config file //add each group of spawn areas to the list (unless its config section is null) if (config.Forage_Spawn_Settings != null) { allAreas.Add(config.Forage_Spawn_Settings.Areas); } if (config.Large_Object_Spawn_Settings != null) { allAreas.Add(config.Large_Object_Spawn_Settings.Areas); } if (config.Ore_Spawn_Settings != null) { allAreas.Add(config.Ore_Spawn_Settings.Areas); } if (config.Monster_Spawn_Settings != null) { allAreas.Add(config.Monster_Spawn_Settings.Areas); } Monitor.Log("Checking for duplicate UniqueAreaIDs...", LogLevel.Trace); HashSet <string> IDs = new HashSet <string>(); //a record of all unique IDs encountered during this process //erase any duplicate IDs and record the others in the "IDs" hashset foreach (SpawnArea[] areas in allAreas) //for each "Areas" array in allAreas { foreach (SpawnArea area in areas) //for each area in the current array { if (String.IsNullOrWhiteSpace(area.UniqueAreaID) || area.UniqueAreaID.ToLower() == "null") //if the area ID is null, blank, or the string "null" (to account for user confusion) { continue; //this name will be replaced later, so ignore it for now } if (IDs.Contains(area.UniqueAreaID)) //if this area's ID was already encountered { Monitor.Log($"Duplicate UniqueAreaID found: \"{area.UniqueAreaID}\" will be renamed.", LogLevel.Debug); if (pack != null) //if this config is from a content pack { Monitor.Log($"Content pack: {pack.Manifest.Name}", LogLevel.Info); Monitor.Log($"If this happened after updating another mod, it might cause certain conditions (such as one-time-only spawns) to reset in that area.", LogLevel.Debug); } area.UniqueAreaID = ""; //erase this area's ID, marking it for replacement } else //if this ID is unique so far { IDs.Add(area.UniqueAreaID); //add the area to the ID set } } } Monitor.Log("Assigning new UniqueAreaIDs to any blanks or duplicates...", LogLevel.Trace); string newName; //temp storage for a new ID while it's created/tested int newNumber; //temp storage for the numeric part of a new ID //create new IDs for any empty ones foreach (SpawnArea[] areas in allAreas) //for each "Areas" array in allAreas { foreach (SpawnArea area in areas) //for each area in the current array { if (String.IsNullOrWhiteSpace(area.UniqueAreaID) || area.UniqueAreaID.ToLower() == "null") //if the area ID is null, blank, or the string "null" (to account for user confusion) { //create a new name, based on which type of area this is newName = area.MapName; if (area is ForageSpawnArea) { newName += " forage area "; } else if (area is LargeObjectSpawnArea) { newName += " large object area "; } else if (area is OreSpawnArea) { newName += " ore area "; } else if (area is MonsterSpawnArea) { newName += " monster area "; } else { newName += " area "; } newNumber = 1; while (IDs.Contains(newName + newNumber)) //if this ID wouldn't be unique { newNumber++; //increment and try again } area.UniqueAreaID = newName + newNumber; //apply the new unique ID Monitor.Log($"New UniqueAreaID assigned: {area.UniqueAreaID}", LogLevel.Trace); } IDs.Add(area.UniqueAreaID); //the ID is finalized, so add it to the set of encountered IDs } } //confirm that any paired min/max settings are in the correct order foreach (SpawnArea[] areas in allAreas) //for each "Areas" array in allAreas { foreach (SpawnArea area in areas) //for each area in the current array { if (area.MinimumSpawnsPerDay > area.MaximumSpawnsPerDay) //if the min and max are in the wrong order { //swap min and max int temp = area.MinimumSpawnsPerDay; area.MinimumSpawnsPerDay = area.MaximumSpawnsPerDay; area.MaximumSpawnsPerDay = temp; Monitor.Log($"Swapping minimum and maximum spawns per day for this area: {area.UniqueAreaID}", LogLevel.Trace); } if (area.SpawnTiming.StartTime > area.SpawnTiming.EndTime) //if start and end are in the wrong order { //swap start and end StardewTime temp = area.SpawnTiming.StartTime; area.SpawnTiming.StartTime = area.SpawnTiming.EndTime; area.SpawnTiming.EndTime = temp; Monitor.Log($"Swapping StartTime and EndTime in the SpawnTiming settings for this area: {area.UniqueAreaID}", LogLevel.Trace); } } } //detect invalid sound names and warn the user //NOTE: this will not remove the invalid name, in case the problem is related to custom sound loading foreach (SpawnArea[] areas in allAreas) //for each "Areas" array in allAreas { foreach (SpawnArea area in areas) //for each area in the current array { if (area.SpawnTiming.SpawnSound != null && area.SpawnTiming.SpawnSound.Trim() != "") //if a SpawnSound has been provided for this area { try { Game1.soundBank.GetCue(area.SpawnTiming.SpawnSound); //test whether this sound exists by retrieving it from the game's soundbank } catch //if an exception is thrown while retrieving the sound { Monitor.Log($"This spawn sound could not be found: {area.SpawnTiming.SpawnSound}", LogLevel.Debug); Monitor.Log($"Please make sure the sound's name is spelled and capitalized correctly. Sound names are case-sensitive.", LogLevel.Debug); Monitor.Log($"Area: {area.UniqueAreaID}", LogLevel.Debug); if (pack != null) //if this file is from a content pack { Monitor.Log($"Content pack: {pack.Manifest.Name}", LogLevel.Debug); } else //if this file is from FarmTypeManager/data { Monitor.Log($"File: FarmTypeManager/data/{Constants.SaveFolderName}.json", LogLevel.Debug); } } } } } if (pack != null) { Monitor.Log($"Validation complete for content pack: {pack.Manifest.Name}", LogLevel.Trace); } else { Monitor.Log("Validation complete for data from FarmTypeManager/data", LogLevel.Trace); } return; }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="indexPath">The path of indexes from the root <c>content.json</c> to this patch; see <see cref="IPatch.IndexPath"/>.</param> /// <param name="path">The path to the patch from the root content file.</param> /// <param name="assetName">The normalized asset name to intercept.</param> /// <param name="localAsset">The asset key to load from the content pack instead.</param> /// <param name="conditions">The conditions which determine whether this patch should be applied.</param> /// <param name="updateRate">When the patch should be updated.</param> /// <param name="contentPack">The content pack which requested the patch.</param> /// <param name="parentPatch">The parent patch for which this patch was loaded, if any.</param> /// <param name="parseAssetName">Parse an asset name.</param> public LoadPatch(int[] indexPath, LogPathBuilder path, IManagedTokenString assetName, IManagedTokenString localAsset, IEnumerable <Condition> conditions, UpdateRate updateRate, IContentPack contentPack, IPatch?parentPatch, Func <string, IAssetName> parseAssetName) : base( indexPath: indexPath, path: path, type: PatchType.Load, assetName: assetName, conditions: conditions, updateRate: updateRate, contentPack: contentPack, parentPatch: parentPatch, parseAssetName: parseAssetName, fromAsset: localAsset ) { }
/********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="logName">A unique name for this patch shown in log messages.</param> /// <param name="assetLoader">Handles loading assets from content packs.</param> /// <param name="contentPack">The content pack which requested the patch.</param> /// <param name="assetName">The normalised asset name to intercept.</param> /// <param name="conditions">The conditions which determine whether this patch should be applied.</param> /// <param name="localAsset">The asset key to load from the content pack instead.</param> /// <param name="normaliseAssetName">Normalise an asset name.</param> public LoadPatch(string logName, AssetLoader assetLoader, IContentPack contentPack, TokenString assetName, ConditionDictionary conditions, TokenString localAsset, Func <string, string> normaliseAssetName) : base(logName, PatchType.Load, assetLoader, contentPack, assetName, conditions, normaliseAssetName) { this.LocalAsset = localAsset; }