/// <summary> /// /// </summary> /// <param name="contentPack"></param> /// <param name="farm"></param> /// <returns></returns> private bool ProcessContentPack(IContentPack contentPack, out CustomFarm farm) { Dictionary <string, object> Extra; bool results; if (contentPack.Manifest.ExtraFields != null && contentPack.Manifest.ExtraFields.ContainsKey("ContentPackType")) { Extra = (Dictionary <string, object>)ObjectToDictionaryHelper.ToDictionary(contentPack.Manifest.ExtraFields["ContentPackType"]); if (Extra.ContainsKey("Farm") && bool.Parse(Extra["Farm"].ToString())) { farm = contentPack.ReadJsonFile <CustomFarm>("farmType.json"); results = true; } else { farm = null; results = false; } } else { farm = contentPack.ReadJsonFile <CustomFarm>("farmType.json"); results = true; } return(results); }
public static bool LoadContentPack(IContentPack contentPack, EventArgs e) { string contentPackConfigJson = GetActualCaseForFileName(contentPack.DirectoryPath, ContentPackConfigJson); bool haveContentPackConfigFile = contentPackConfigJson != null; if (haveContentPackConfigFile) { ContentPackConfig contentPackConfig = contentPack.ReadJsonFile <ContentPackConfig>(contentPackConfigJson); ContentPackConfigController.AddConfig(contentPackConfig, contentPack.Manifest.UniqueID); } else { ProducerFrameworkModEntry.ModMonitor.Log($"Content pack: {contentPack.Manifest.Name} {contentPack.Manifest.Version} from {contentPack.DirectoryPath}\nIt does not have an {ContentPackConfigJson} file.", LogLevel.Trace); } string producersConfigJson = GetActualCaseForFileName(contentPack.DirectoryPath, ProducersConfigJson); bool haveProducersConfigFile = producersConfigJson != null; ProducerFrameworkModEntry.ModMonitor.Log($"Reading content pack: {contentPack.Manifest.Name} {contentPack.Manifest.Version} from {contentPack.DirectoryPath}"); if (haveProducersConfigFile) { List <ProducerConfig> producersConfigs = contentPack.ReadJsonFile <List <ProducerConfig> >(producersConfigJson); ProducerController.AddProducersConfig(producersConfigs, contentPack.Manifest.UniqueID); } else { ProducerFrameworkModEntry.ModMonitor.Log($"Content pack: {contentPack.Manifest.Name} {contentPack.Manifest.Version} from {contentPack.DirectoryPath}\nIt does not have an {ProducersConfigJson} file.", LogLevel.Trace); } if (e is SaveLoadedEventArgs) { string producerRulesJson = GetActualCaseForFileName(contentPack.DirectoryPath, ProducerRulesJson); bool haveProducerRulesFile = producerRulesJson != null; if (haveProducerRulesFile) { List <ProducerRule> producerItems = contentPack.ReadJsonFile <List <ProducerRule> >(producerRulesJson); ProducerController.AddProducerItems(producerItems, contentPack.Translation, contentPack.Manifest.UniqueID); } else { ProducerFrameworkModEntry.ModMonitor.Log($"Content pack: {contentPack.Manifest.Name} {contentPack.Manifest.Version} from {contentPack.DirectoryPath}\nIt does not have an {ProducerRulesJson} file.", LogLevel.Trace); } if (!haveProducerRulesFile && !haveProducersConfigFile) { ProducerFrameworkModEntry.ModMonitor.Log($"Ignoring content pack: {contentPack.Manifest.Name} {contentPack.Manifest.Version} from {contentPack.DirectoryPath}\nIt does not have any of the required files.", LogLevel.Warn); return(false); } } return(true); }
private void compileChoices() { Log.info("Creating list of custom farm types..."); var farmTypeDirs = Directory.GetDirectories(Path.Combine(Helper.DirectoryPath, "assets", "FarmTypes")); foreach (var folderPath in farmTypeDirs) { IContentPack contentPack = this.Helper.ContentPacks.CreateFake(folderPath); if (!File.Exists(Path.Combine(folderPath, "type.json")) || !File.Exists(Path.Combine(folderPath, "map.xnb")) || !File.Exists(Path.Combine(folderPath, "icon.png"))) { Log.error($"A required file is missing for custom farm type \"{folderPath}\"."); continue; } FarmType type = contentPack.ReadJsonFile <FarmType>("type.json"); if (type == null) { Log.error($"Problem reading type.json for custom farm type \"{folderPath}\"."); continue; } type.Folder = Path.Combine("assets", "FarmTypes", Path.GetFileName(folderPath)); FarmType.register(type); Log.info($"\tFarm type: {type.Name} ({type.ID})"); } }
private void LoadMonsters(IContentPack contentPack) { Monitor.Log($"Loading Content Pack: {contentPack.Manifest.Name}{contentPack.Manifest.Version} by {contentPack.Manifest.Author}"); //Start loading custom monsters. DirectoryInfo monInfo = new DirectoryInfo(Path.Combine(contentPack.DirectoryPath, "Monsters")); if (monInfo.Exists) { foreach (var dir in monInfo.EnumerateDirectories()) { string relPath = $"Monsters/{dir.Name}"; mData = contentPack.ReadJsonFile <MonsterData>($"{relPath}/monster.json"); if (mData == null) { continue; } mData.mSprite = contentPack.LoadAsset <AnimatedSprite>($"{relPath}/monster.png"); mData.mSpriteStr = $"{relPath}/monster.png"; this.monsters.Add(mData); //Load the data up. } } }
internal void LoadPPaFConfig() { PPaFModInfo = Helper.ModRegistry.Get("Amaranthacyan.PlatonicPartnersandFriendships"); if (PPaFModInfo == null) { Monitor.Log($"PPaF not installed, cannot sync config"); return; } PropertyInfo property = PPaFModInfo.GetType().GetProperty("DirectoryPath", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); if (property == null) { Monitor.Log($"Can't access directory path for PPaF", LogLevel.Error); return; } string directoryPath = property.GetValue(PPaFModInfo).ToString(); Monitor.Log($"Loading PPaF config from {directoryPath}", LogLevel.Debug); IContentPack contentPack = this.Helper.ContentPacks.CreateFake(directoryPath); PPaFModConfig pmc = contentPack.ReadJsonFile <PPaFModConfig>("config.json"); if (pmc.PlatonicNPCs.Length > 0) { Monitor.Log($"Platonic NPCs: {pmc.PlatonicNPCs}", LogLevel.Debug); Config.HuggingNPCs = Config.cslToList(pmc.PlatonicNPCs); UsingPPaFConfig = true; } }
/// <summary>Load config values from the content pack.</summary> /// <param name="contentPack">The content pack whose config file to read.</param> /// <param name="config">The config schema.</param> /// <param name="logWarning">The callback to invoke on each validation warning, passed the field name and reason respectively.</param> private void LoadConfigValues(IContentPack contentPack, InvariantDictionary <ConfigField> config, Action <string, string> logWarning) { if (!config.Any()) { return; } // read raw config InvariantDictionary <InvariantHashSet> configValues = new InvariantDictionary <InvariantHashSet>( from entry in (contentPack.ReadJsonFile <InvariantDictionary <string> >(this.Filename) ?? new InvariantDictionary <string>()) let key = entry.Key.Trim() let value = this.ParseCommaDelimitedField(entry.Value) select new KeyValuePair <string, InvariantHashSet>(key, value) ); // remove invalid values foreach (string key in configValues.Keys.ExceptIgnoreCase(config.Keys).ToArray()) { logWarning(key, "no such field supported by this content pack."); configValues.Remove(key); } // inject default values foreach (string key in config.Keys) { ConfigField field = config[key]; if (!configValues.TryGetValue(key, out InvariantHashSet values) || (!field.AllowBlank && !values.Any())) { configValues[key] = field.DefaultValues; } } // parse each field foreach (string key in config.Keys) { // set value ConfigField field = config[key]; field.Value = configValues[key]; // validate allow-multiple if (!field.AllowMultiple && field.Value.Count > 1) { logWarning(key, "field only allows a single value."); field.Value = field.DefaultValues; continue; } // validate allow-values if (field.AllowValues.Any()) { string[] invalidValues = field.Value.ExceptIgnoreCase(field.AllowValues).ToArray(); if (invalidValues.Any()) { logWarning(key, $"found invalid values ({string.Join(", ", invalidValues)}), expected: {string.Join(", ", field.AllowValues)}."); field.Value = field.DefaultValues; } } } }
public override void Entry(IModHelper helper) { var harmony = new Harmony(this.ModManifest.UniqueID); // Try and read config, use default values if unable try { this.config = helper.ReadConfig <ModConfig>(); } catch (Exception ex) { this.config = new ModConfig(); this.Monitor.Log("Error reading config, using default values...", LogLevel.Warn); this.Monitor.Log($"An error occured reading the config. Details:\n{ex}"); } // Add harmony patches Patches.Hook(harmony, this.Monitor, this.config, this.InternalExceptions); // Allow EventLimiterapi access to needed values EventLimiterApi.Hook(this.config, this.InternalExceptions); foreach (IModInfo mod in this.Helper.ModRegistry.GetAll()) { // Check if it's a Content Patcher pack if (mod.IsContentPack == false || mod.Manifest.ContentPackFor?.UniqueID.Trim().Equals("Pathoschild.ContentPatcher", StringComparison.InvariantCultureIgnoreCase) == false) { continue; } // Use reflection on IModInfo to get non-public property string directoryPath = (string)mod.GetType().GetProperty("DirectoryPath")?.GetValue(mod); if (directoryPath == null) { throw new InvalidOperationException($"Couldn't get DirectoryPath property from the mod info for {mod.Manifest.Name}."); } // read JSON file into data model IContentPack contentPack = this.Helper.ContentPacks.CreateFake(directoryPath); InternalExceptionModel model = contentPack.ReadJsonFile <InternalExceptionModel>("content.json"); // Get event IDs from model and add to internal exceptions if (model?.EventLimiterExceptions != null) { foreach (int eventid in model.EventLimiterExceptions) { this.InternalExceptions.Add(eventid); this.Monitor.Log($"Content pack {mod.Manifest.Name} added event {eventid} as event limit exception"); } } } // Add event handlers helper.Events.GameLoop.GameLaunched += this.GameLaunched; helper.Events.GameLoop.DayStarted += this.DayStarted; helper.Events.Input.ButtonPressed += this.ButtonPressed; }
/// <summary> /// Converts a content pack with a farmType.json 2.0 to the latest. /// </summary> /// <param name="contentPack">A SMAPI Content Pack</param> /// <param name="monitor">SMAPI's IMonitor, to print useful information.</param> /// <returns>Converted CustomFarm</returns> private CustomFarm PopulateVerison2dot0(IContentPack contentPack, IMonitor monitor) { CustomFarmVer2p0 oldVersion; CustomFarm convertedFarm; monitor.Log("\t - Content Pack is using FarmType 2.0. Using Backwards Compatibility."); oldVersion = contentPack.ReadJsonFile <CustomFarmVer2p0>("farmType.json"); convertedFarm = new CustomFarm(); CustomFarmVer2p0.Convert(convertedFarm, oldVersion); return(convertedFarm); }
/// <summary> /// Converts a content pack with a farmType.json 1.0 (MTN1) to the latest. /// </summary> /// <param name="contentPack">A SMAPI Content Pack</param> /// <param name="monitor">SMAPI's IMonitor, to print useful information.</param> /// <returns>Converted CustomFarm</returns> private CustomFarm PopulateVersion1dot0(IContentPack contentPack, IMonitor monitor) { CustomFarmVer1 oldVersion; CustomFarm convertedFarm; monitor.Log("\t - Content Pack is for FarmType 1.0 (MTN1). Using Backwards Compatibility."); oldVersion = contentPack.ReadJsonFile <CustomFarmVer1>("farmType.json"); convertedFarm = new CustomFarm(); CustomFarmVer1.Convert(convertedFarm, oldVersion); return(convertedFarm); }
/********* ** Protected methods *********/ /// <summary>Read config data from a content pack.</summary> /// <param name="contentPack">The content pack to load.</param> private LocationConfig ReadConfig(IContentPack contentPack) { // get config path string configPath; if (File.Exists(Path.Combine(contentPack.DirectoryPath, "locations.json"))) { configPath = "locations.json"; // SMAPI content pack } else if (File.Exists(Path.Combine(contentPack.DirectoryPath, "manifest.json"))) { configPath = "manifest.json"; // ALL location mod } else { this.Monitor.Log(" Skipped: can't find a locations.json or manifest.json file.", LogLevel.Error); return(null); } // get format version ISemanticVersion formatVersion; try { string rawVersion = contentPack.ReadJsonFile <LoaderVersionConfig>(configPath)?.LoaderVersion; if (rawVersion == null) { this.Monitor.Log($" Skipped: config doesn't specify a {nameof(LoaderVersionConfig.LoaderVersion)} field.", LogLevel.Error); return(null); } formatVersion = new SemanticVersion(rawVersion); } catch (Exception ex) { this.Monitor.Log(" Skipped: can't parse config file to check loader version.", LogLevel.Error, ex); return(null); } // read data switch ($"{formatVersion.MajorVersion}.{formatVersion.MinorVersion}") { case "1.1": return(this.ReadConfig_1_1(contentPack, configPath)); case "1.2": return(this.ReadConfig_1_2(contentPack, configPath)); default: this.Monitor.Log($"Skipped {contentPack.Manifest.Name}: config file format {formatVersion} isn't supported, must be 1.1 or 1.2.", LogLevel.Error); return(null); } }
public bool AddContentPack(IContentPack contentPack) { try { Monitor.Log($"Reading content pack: {contentPack.Manifest.Name} {contentPack.Manifest.Version} from {contentPack.DirectoryPath}"); DiveMapData data = contentPack.ReadJsonFile <DiveMapData>("content.json"); SwimUtils.ReadDiveMapData(data); return(true); } catch { Monitor.Log($"couldn't read content.json in content pack {contentPack.Manifest.Name}", LogLevel.Warn); return(false); } }
private Content LoadContentPack(IContentPack contentPack) { if (!contentPack.HasFile("quests.json")) { this.Monitor.Log($"Content pack `{contentPack.Manifest.Name}` has no entry file `quests.json`", LogLevel.Error); return(null); } var content = contentPack.ReadJsonFile <Content>("quests.json"); content.Owner = contentPack; this.Prepare(content); return(content); }
/// <summary> /// /// </summary> /// <param name="contentPack"></param> /// <param name="greenHouse"></param> /// <returns></returns> private bool ProcessContentPack(IContentPack contentPack, out CustomGreenHouse greenHouse) { Dictionary <string, object> Extra; if (contentPack.Manifest.ExtraFields != null && contentPack.Manifest.ExtraFields.ContainsKey("ContentPackType")) { Extra = (Dictionary <string, object>)ObjectToDictionaryHelper.ToDictionary(contentPack.Manifest.ExtraFields["ContentPackType"]); if (Extra.ContainsKey("Greenhouse") && bool.Parse(Extra["Greenhouse"].ToString())) { greenHouse = contentPack.ReadJsonFile <CustomGreenHouse>("greenHouseType.json"); return(true); } } greenHouse = null; return(false); }
private void loadData(string dir) { // read initial info IContentPack temp = this.Helper.ContentPacks.CreateFake(dir); ContentPackData info = temp.ReadJsonFile <ContentPackData>("content-pack.json"); if (info == null) { Log.warn($"\tNo {dir}/content-pack.json!"); return; } // load content pack IContentPack contentPack = this.Helper.ContentPacks.CreateTemporary(dir, id: Guid.NewGuid().ToString("N"), name: info.Name, description: info.Description, author: info.Author, version: new SemanticVersion(info.Version)); this.loadData(contentPack); }
public bool AddContentPack(string directory) { Regex nameToId = new Regex("[^a-zA-Z0-9_.]"); ProducerFrameworkModEntry.ModMonitor.Log($"Reading content pack called through the API from {directory}"); IContentPack temp = ProducerFrameworkModEntry.Helper.ContentPacks.CreateFake(directory); ManifestData info = temp.ReadJsonFile <ManifestData>("content-pack.json"); if (info == null) { ProducerFrameworkModEntry.ModMonitor.Log($"\tNo content-pack.json found in {directory}!", LogLevel.Warn); return(false); } string id = info.UniqueID ?? nameToId.Replace(info.Author + "." + info.Name, ""); IContentPack contentPack = ProducerFrameworkModEntry.Helper.ContentPacks.CreateTemporary(directory, id, info.Name, info.Description, info.Author, new SemanticVersion(info.Version)); return(DataLoader.LoadContentPack(contentPack, new SaveLoadedEventArgs())); }
/// <summary>Get the current values.</summary> /// <param name="input">The input argument, if applicable.</param> public IEnumerable <string> GetValues(string input) { RecolorTokenArguments inputData = RecolorTokenArguments.Parse(input); IContentPack contentPack = Utility.GetContentPackFromModInfo(helper_.ModRegistry.Get(inputData.ContentPackName)); // ATTENTION: In order to load files we just generated we need at least ContentPatcher 1.18.3 . string generatedFilePath = GenerateFilePath(inputData); string generatedFilePathAbsolute = Path.Combine(contentPack.DirectoryPath, generatedFilePath); if (!(inputData.Equals(previousInputData_))) { mustUpdateContext_ = true; previousInputData_ = inputData; monitor_.Log($"Content pack {contentPack.Manifest.UniqueID} requests recoloring of {inputData.AssetName}."); monitor_.Log($"Recolor with {inputData.MaskPath} and {Utility.ColorToHtml(inputData.BlendColor)}, flip mode {inputData.FlipMode}, brightness {inputData.Brightness}"); // Check versions: If version of SDV or content pack changed we have to delete generated images. string contentPackVersion = contentPack.Manifest.Version.ToString(); string generatedDirectoryPathAbsolute = Path.Combine(contentPack.DirectoryPath, "generated"); var versions = contentPack.ReadJsonFile <Dictionary <string, string> >("generated/versions.json") ?? new Dictionary <string, string>(); if (!versions.TryGetValue("StardewValley", out string stardewVersion) || stardewVersion != Game1.version) { versions["StardewValley"] = Game1.version; monitor_.Log($"Version of StardewValley changed from {stardewVersion ?? "(null)"} to {Game1.version}, deleting generated files."); if (Directory.Exists(generatedDirectoryPathAbsolute)) { Directory.Delete(generatedDirectoryPathAbsolute, true); } contentPack.WriteJsonFile("generated/versions.json", versions); } if (!versions.TryGetValue(contentPack.Manifest.UniqueID, out string modVersion) || modVersion != contentPackVersion) { versions[contentPack.Manifest.UniqueID] = contentPackVersion; monitor_.Log($"Version of content pack {contentPack.Manifest.UniqueID} changed from {modVersion ?? "(null)"} to {contentPackVersion}, deleting generated files."); if (Directory.Exists(generatedDirectoryPathAbsolute)) { Directory.Delete(generatedDirectoryPathAbsolute, true); } contentPack.WriteJsonFile("generated/versions.json", versions); } // Skip actions if file was found. if (File.Exists(generatedFilePathAbsolute)) { monitor_.Log($"Found existing file {generatedFilePathAbsolute}, returning relative path {generatedFilePath}"); helper_.Content.InvalidateCache(inputData.AssetName); } else { try { // "gamecontent" means loading from game folder. Don't dispose an asset source! Texture2D source; if (inputData.SourcePath.ToLowerInvariant() == "gamecontent") { // ATTENTION: Game content requires special attention because the loaded assets modify themselves over time: // Game content contains vanilla assets only when game is loaded, otherwise the assets are already patched. // Luma desaturation and recoloring don't change brightness so they can be applied multiple times // without bad effects but changing brightness multiple times changes colors over time. // A way to prevent that is caching them in files once and using the cached versions as a base for modifications. string generatedBasePath = Path.Combine("generated", $"{inputData.AssetName}_gamecontent.png"); string generatedBasePathAbsolute = Path.Combine(contentPack.DirectoryPath, generatedBasePath); Directory.CreateDirectory(Path.GetDirectoryName(generatedBasePathAbsolute)); if (File.Exists(generatedBasePathAbsolute)) { using (FileStream fs = new FileStream(generatedBasePathAbsolute, FileMode.Open)) { source = Texture2D.FromStream(Game1.graphics.GraphicsDevice, fs); } monitor_.Log($"Loading asset {inputData.AssetName} from existing cache file {generatedBasePathAbsolute}"); } else { source = helper_.Content.Load <Texture2D>(inputData.AssetName, ContentSource.GameContent); using (FileStream fs = new FileStream(generatedBasePathAbsolute, FileMode.Create)) { source.SaveAsPng(fs, source.Width, source.Height); fs.Close(); } monitor_.Log($"Saving asset {inputData.AssetName} in cache file {generatedBasePathAbsolute}"); } } else { source = contentPack.LoadAsset <Texture2D>(inputData.SourcePath); } Texture2D mask = inputData.MaskPath.ToLowerInvariant() != "none" ? contentPack.LoadAsset <Texture2D>(inputData.MaskPath) : null; using (Texture2D extracted = ExtractSubImage(source, mask, inputData.DesaturationMode, inputData.Brightness)) using (Texture2D blended = ColorBlend(extracted, inputData.BlendColor)) using (Texture2D flipped = FlipImage(source, blended, inputData.FlipMode)) { Directory.CreateDirectory(Path.GetDirectoryName(generatedFilePathAbsolute)); using (FileStream fs = new FileStream(generatedFilePathAbsolute, FileMode.Create)) { flipped.SaveAsPng(fs, flipped.Width, flipped.Height); fs.Close(); } } monitor_.Log($"Generated file {generatedFilePathAbsolute}, returning relative path {generatedFilePath}"); helper_.Content.InvalidateCache(inputData.AssetName); } catch (ContentLoadException) { // Asset is not available, return its name to prevent game from crashing. monitor_.Log($"Ignoring unavailable asset {inputData.AssetName}. If this was caused by patch reload you can ignore it, the next 10min update cycle should do a proper reload.", LogLevel.Info); generatedFilePath = inputData.AssetName; } } } yield return(generatedFilePath); }
/// <summary> /// Creates a new Hair Model. /// </summary> private void CreateNewHairModel() { Hair = CurrentContentPack.ReadJsonFile <HairModel>("Hairstyles/hairstyles.json"); Hair.Texture = CurrentContentPack.LoadAsset <Texture2D>("Hairstyles/hairstyles.png"); }
/// <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) { instance = this; helper.Events.Display.MenuChanged += OnMenuChanged; var savedIds = helper.Data.ReadJsonFile <Dictionary <string, CropData.Ids> >("saved-ids.json"); if (savedIds != null) { CropData.savedIds = savedIds; foreach (var ids in savedIds) { CropData.Ids.MostRecentObject = Math.Max(CropData.Ids.MostRecentObject, Math.Max(ids.Value.Product, ids.Value.Seeds)); CropData.Ids.MostRecentCrop = Math.Max(CropData.Ids.MostRecentCrop, ids.Value.Crop); } } CropData.crops.Clear(); Log.info("Registering custom crops..."); DirectoryInfo cropsFolder = new DirectoryInfo(Path.Combine(helper.DirectoryPath, "Crops")); if (!cropsFolder.Exists) { cropsFolder.Create(); } foreach (var folderPath in Directory.EnumerateDirectories(cropsFolder.FullName)) { IContentPack contentPack = this.Helper.ContentPacks.CreateFake(folderPath); try { var data = contentPack.ReadJsonFile <CropData>("crop.json"); if (data == null) { Log.warn($"\tFailed to load crop data for {folderPath}"); continue; } else if (!File.Exists(Path.Combine(folderPath, "crop.png"))) { Log.warn($"\tCrop {folderPath} has no crop image, skipping"); continue; } else if (!File.Exists(Path.Combine(folderPath, "product.png"))) { Log.warn($"\tCrop {folderPath} has no product image, skipping"); continue; } else if (!File.Exists(Path.Combine(folderPath, "seeds.png"))) { Log.warn($"\tCrop {folderPath} has no seeds image, skipping"); continue; } Log.info($"\tCrop: {data.Id}"); CropData.Register(data); } catch (Exception e) { Log.warn($"\tFailed to load crop data for {folderPath}: {e}"); continue; } } helper.Data.WriteJsonFile("saved-ids.json", CropData.savedIds); helper.Content.AssetEditors.Add(new ContentInjector()); }
/// <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 var 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 (var entry in childConfig.Conditionals) { config.Conditionals.Add(entry); } foreach (var entry in childConfig.Locations) { config.Locations.Add(entry); } foreach (var entry in childConfig.Overrides) { config.Overrides.Add(entry); } foreach (var entry in childConfig.Properties) { config.Properties.Add(entry); } foreach (var entry in childConfig.Redirects) { config.Redirects.Add(entry); } foreach (var entry in childConfig.Shops) { config.Shops.Add(entry); } foreach (var entry in childConfig.Teleporters) { config.Teleporters.Add(entry); } foreach (var entry in childConfig.Tiles) { config.Tiles.Add(entry); } foreach (var entry in childConfig.Tilesheets) { config.Tilesheets.Add(entry); } foreach (var 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>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); }
/// <summary>Checks whether a config file should be used with the currently loaded farm.</summary> /// <param name="config">The FarmConfig to be checked.</param> /// <returns>True if the file should be used with the current farm; false otherwise.</returns> public static bool CheckFileConditions(FarmConfig config, IContentPack pack, IModHelper helper) { 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 (string type in config.File_Conditions.FarmTypes) //for each listed farm type { if (type.Equals("All", StringComparison.OrdinalIgnoreCase) || type.Equals("Any", StringComparison.OrdinalIgnoreCase)) //if "all" or "any" is listed { validType = true; break; //skip the rest of these checks } switch (Game1.whichFarm) //compare to the current farm type { case (int)Utility.FarmTypes.Standard: if (type.Equals("Standard", StringComparison.OrdinalIgnoreCase) || type.Equals("Default", StringComparison.OrdinalIgnoreCase) || type.Equals("Normal", StringComparison.OrdinalIgnoreCase)) { validType = true; } break; case (int)Utility.FarmTypes.Riverland: if (type.Equals("Riverland", StringComparison.OrdinalIgnoreCase) || type.Equals("Fishing", StringComparison.OrdinalIgnoreCase) || type.Equals("Fish", StringComparison.OrdinalIgnoreCase)) { validType = true; } break; case (int)Utility.FarmTypes.Forest: if (type.Equals("Forest", StringComparison.OrdinalIgnoreCase) || type.Equals("Foraging", StringComparison.OrdinalIgnoreCase) || type.Equals("Forage", StringComparison.OrdinalIgnoreCase) || type.Equals("Woodland", StringComparison.OrdinalIgnoreCase)) { validType = true; } break; case (int)Utility.FarmTypes.Hilltop: if (type.Equals("Hill-top", StringComparison.OrdinalIgnoreCase) || type.Equals("Hilltop", StringComparison.OrdinalIgnoreCase) || type.Equals("Mining", StringComparison.OrdinalIgnoreCase) || type.Equals("Mine", StringComparison.OrdinalIgnoreCase)) { validType = true; } break; case (int)Utility.FarmTypes.Wilderness: if (type.Equals("Wilderness", StringComparison.OrdinalIgnoreCase) || type.Equals("Combat", StringComparison.OrdinalIgnoreCase) || type.Equals("Monster", StringComparison.OrdinalIgnoreCase)) { validType = true; } break; } if (validType) //if a valid weather condition was listed { break; //skip the rest of these checks } } 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 }
/// <summary>Load the passed content pack.</summary> /// <param name="contentPack">The content pack to load.</param> private void LoadContentPack(IContentPack contentPack) { this.Monitor.Log($"Loading content pack: {contentPack.Manifest.Name}", LogLevel.Info); // load each tree var modDirectory = new DirectoryInfo(contentPack.DirectoryPath); foreach (var treePath in modDirectory.EnumerateDirectories()) { // ensure tree.png exists var isValid = true; if (!File.Exists(Path.Combine(treePath.FullName, "tree.png"))) { this.Monitor.Log($"tree.png couldn't be found for {contentPack.Manifest.Name}.", LogLevel.Error); isValid = false; } // ensure content.json exists if (!File.Exists(Path.Combine(treePath.FullName, "content.json"))) { this.Monitor.Log($"content.json couldn't be found for {contentPack.Manifest.Name}.", LogLevel.Error); isValid = false; } if (!isValid) { continue; } var treeTexture = contentPack.LoadAsset <Texture2D>(Path.Combine(treePath.Name, "tree.png")); var treeData = contentPack.ReadJsonFile <TreeData>(Path.Combine(treePath.Name, "content.json")); if (treeData == null) { this.Monitor.Log($"Content.json couldn't be found for: {treePath.Name}.", LogLevel.Error); continue; } treeData.ResolveTokens(); if (!treeData.IsValid()) { this.Monitor.Log($"Validation for treeData for: {treePath.Name} failed, skipping.", LogLevel.Error); continue; } // ensure the tree can be loaded (using IncludeIfModIsPresent) { var loadTree = true; if (treeData.IncludeIfModIsPresent != null && treeData.IncludeIfModIsPresent.Count > 0) { // set this to false so it can be set to true if a required mod is found loadTree = false; foreach (var requiredMod in treeData.IncludeIfModIsPresent) { if (!this.Helper.ModRegistry.IsLoaded(requiredMod)) { continue; } loadTree = true; break; } } if (!loadTree) { this.Monitor.Log("Tree won't get loaded as no mods specified in 'IncludeIfModIsPresent' were present.", LogLevel.Info); continue; } } // ensure the tree can be loaded (using ExcludeIfModIsPresent) { var loadTree = true; if (treeData.ExcludeIfModIsPresent != null && treeData.ExcludeIfModIsPresent.Count > 0) { foreach (var unwantedMod in treeData.ExcludeIfModIsPresent) { if (!this.Helper.ModRegistry.IsLoaded(unwantedMod)) { continue; } loadTree = false; break; } } if (!loadTree) { this.Monitor.Log("Tree won't get loaded as a mod specified in 'ExcludeIfModIsPresent' was present.", LogLevel.Info); continue; } } // ensure the tree hasn't been added by another mod if (LoadedTrees.Where(tree => tree.Name.ToLower() == treePath.Name.ToLower()).Any()) { this.Monitor.Log($"A tree by the name: {treePath.Name} has already been added.", LogLevel.Error); continue; } // get the tree type, use the api as they're save persitant var treeType = Api.GetTreeType(treePath.Name); // add the tree to the loaded trees LoadedTrees.Add(new CustomTree(treeType, treePath.Name, treeData, treeTexture)); } }
public ContentPackSource(IContentPack pack) { this.pack = pack; TextureData = pack.ReadJsonFile <CustomTextureData>("data.json"); }
/********* ** 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>(); // models.Add(this.Helper.Data.ReadJsonFile<ThingsToForget>("content.json")); //foreach (IContentPack contentPack in this.Helper.ContentPacks.GetOwned()) // models.Add(contentPack.ReadJsonFile<ThingsToForget>("content.json")); 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); }
/// <summary> /// Creates a new face and nose model. /// </summary> private void CreateNewFaceNoseModel() { FaceNose = CurrentContentPack.ReadJsonFile <FaceNoseModel>(Path.Combine("FaceAndNose", "count.json")); }
public void Convert(IContentPack cp, string newModId) { string jaPath = (string)cp.GetType().GetProperty("DirectoryPath", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).GetValue(cp); string dgaPath = Path.Combine(Path.GetDirectoryName(this.Helper.DirectoryPath), "[DGA] " + newModId); Log.Info("Path: " + jaPath + " -> " + dgaPath); if (Directory.Exists(dgaPath)) { Log.Error("Already converted!"); return; } Directory.CreateDirectory(dgaPath); var i18n = new Dictionary <string, Dictionary <string, string> >(); var objs = new List <DynamicGameAssets.PackData.ObjectPackData>(); var crops = new List <DynamicGameAssets.PackData.CropPackData>(); var fruitTrees = new List <DynamicGameAssets.PackData.FruitTreePackData>(); var bigs = new List <DynamicGameAssets.PackData.BigCraftablePackData>(); var hats = new List <DynamicGameAssets.PackData.HatPackData>(); var weapons = new List <DynamicGameAssets.PackData.MeleeWeaponPackData>(); var shirts = new List <DynamicGameAssets.PackData.ShirtPackData>(); var pants = new List <DynamicGameAssets.PackData.PantsPackData>(); var tailoring = new List <DynamicGameAssets.PackData.TailoringRecipePackData>(); var boots = new List <DynamicGameAssets.PackData.BootsPackData>(); var fences = new List <DynamicGameAssets.PackData.FencePackData>(); var forges = new List <DynamicGameAssets.PackData.ForgeRecipePackData>(); var crafting = new List <DynamicGameAssets.PackData.CraftingRecipePackData>(); var shops = new List <DynamicGameAssets.PackData.ShopEntryPackData>(); var giftTastes = new List <DynamicGameAssets.PackData.GiftTastePackData>(); Directory.CreateDirectory(Path.Combine(dgaPath, "assets")); Directory.CreateDirectory(Path.Combine(dgaPath, "assets", "objects")); if (Directory.Exists(Path.Combine(jaPath, "Objects"))) { foreach (string dir_ in Directory.GetDirectories(Path.Combine(jaPath, "Objects"))) { string dir = Path.GetFileName(dir_); if (!File.Exists(Path.Combine(jaPath, "Objects", dir, "object.json"))) { continue; } Log.Trace("Converting object " + dir + "..."); var data = cp.ReadJsonFile <ObjectData>(Path.Combine("Objects", dir, "object.json")); var packData = data.ConvertObject(newModId, i18n, objs, crafting, shops, giftTastes); File.Copy(Path.Combine(jaPath, "Objects", dir, "object.png"), Path.Combine(dgaPath, "assets", "objects", packData.ID + ".png")); if (File.Exists(Path.Combine(jaPath, "Objects", dir, "color.png"))) { File.Copy(Path.Combine(jaPath, "Objects", dir, "color.png"), Path.Combine(dgaPath, "assets", "objects", packData.ID + "_color.png")); } } // pass 2 - crafting recipes // this is necessary because an object could use a later object in its recipe foreach (string dir_ in Directory.GetDirectories(Path.Combine(jaPath, "Objects"))) { string dir = Path.GetFileName(dir_); if (!File.Exists(Path.Combine(jaPath, "Objects", dir, "object.json"))) { continue; } Log.Trace("Converting object crafting recipe " + dir + " (if it exists)..."); var data = cp.ReadJsonFile <ObjectData>(Path.Combine("Objects", dir, "object.json")); var packData = data.ConvertCrafting(newModId, i18n, objs, crafting, shops); } } if (Directory.Exists(Path.Combine(jaPath, "Crops"))) { Directory.CreateDirectory(Path.Combine(dgaPath, "assets", "crops")); foreach (string dir_ in Directory.GetDirectories(Path.Combine(jaPath, "Crops"))) { string dir = Path.GetFileName(dir_); if (!File.Exists(Path.Combine(jaPath, "Crops", dir, "crop.json"))) { continue; } Log.Trace("Converting crop " + dir + "..."); var data = cp.ReadJsonFile <CropData>(Path.Combine("Crops", dir, "crop.json")); var packData = data.ConvertCrop(newModId, i18n, crops, objs, shops); File.Copy(Path.Combine(jaPath, "Crops", dir, "crop.png"), Path.Combine(dgaPath, "assets", "crops", packData.ID + ".png")); if (File.Exists(Path.Combine(jaPath, "Crops", dir, "giant.png"))) { packData.GiantTextureChoices = new string[] { Path.Combine("assets", "crops", packData.ID + "_giant.png") }; packData.GiantDrops.Add((DynamicGameAssets.PackData.CropPackData.HarvestedDropData)packData.Phases[packData.Phases.Count - 1].HarvestedDrops[0].Clone()); File.Copy(Path.Combine(jaPath, "Crops", dir, "giant.png"), Path.Combine(dgaPath, "assets", "crops", packData.ID + "_giant.png")); } if (File.Exists(Path.Combine(jaPath, "Crops", dir, "seeds.png"))) { File.Copy(Path.Combine(jaPath, "Crops", dir, "seeds.png"), Path.Combine(dgaPath, "assets", "objects", packData.ID + "_seeds.png")); } } } if (Directory.Exists(Path.Combine(jaPath, "FruitTrees"))) { Directory.CreateDirectory(Path.Combine(dgaPath, "assets", "fruit-trees")); foreach (string dir_ in Directory.GetDirectories(Path.Combine(jaPath, "FruitTrees"))) { string dir = Path.GetFileName(dir_); if (!File.Exists(Path.Combine(jaPath, "FruitTrees", dir, "tree.json"))) { continue; } Log.Trace("Converting fruit tree " + dir + "..."); var data = cp.ReadJsonFile <FruitTreeData>(Path.Combine("FruitTrees", dir, "tree.json")); var packData = data.ConvertFruitTree(newModId, i18n, fruitTrees, objs, shops); File.Copy(Path.Combine(jaPath, "FruitTrees", dir, "tree.png"), Path.Combine(dgaPath, "assets", "fruit-trees", packData.ID + ".png")); if (File.Exists(Path.Combine(jaPath, "FruitTrees", dir, "sapling.png"))) { File.Copy(Path.Combine(jaPath, "FruitTrees", dir, "sapling.png"), Path.Combine(dgaPath, "assets", "objects", packData.ID + "_sapling.png")); } } } if (Directory.Exists(Path.Combine(jaPath, "BigCraftables"))) { Directory.CreateDirectory(Path.Combine(dgaPath, "assets", "big-craftables")); foreach (string dir_ in Directory.GetDirectories(Path.Combine(jaPath, "BigCraftables"))) { string dir = Path.GetFileName(dir_); if (!File.Exists(Path.Combine(jaPath, "BigCraftables", dir, "big-craftable.json"))) { continue; } Log.Trace("Converting big craftable " + dir + "..."); var data = cp.ReadJsonFile <BigCraftableData>(Path.Combine("BigCraftables", dir, "big-craftable.json")); if (data.ReserveNextIndex && data.ReserveExtraIndexCount == 0) { data.ReserveExtraIndexCount = 1; } var packData = data.ConvertBigCraftable(newModId, i18n, bigs, objs, crafting, shops); File.Copy(Path.Combine(jaPath, "BigCraftables", dir, "big-craftable.png"), Path.Combine(dgaPath, "assets", "big-craftables", packData.ID + "0.png")); for (int i = 0; i < data.ReserveExtraIndexCount; ++i) { File.Copy(Path.Combine(jaPath, "BigCraftables", dir, $"big-craftable-{i + 2}.png"), Path.Combine(dgaPath, "assets", "big-craftables", packData.ID + (i + 1) + ".png")); } } } if (Directory.Exists(Path.Combine(jaPath, "Hats"))) { Directory.CreateDirectory(Path.Combine(dgaPath, "assets", "hats")); foreach (string dir_ in Directory.GetDirectories(Path.Combine(jaPath, "Hats"))) { string dir = Path.GetFileName(dir_); if (!File.Exists(Path.Combine(jaPath, "Hats", dir, "hat.json"))) { continue; } Log.Trace("Converting hat " + dir + "..."); var data = cp.ReadJsonFile <HatData>(Path.Combine("Hats", dir, "hat.json")); var packData = data.ConvertHat(newModId, i18n, hats, shops); File.Copy(Path.Combine(jaPath, "Hats", dir, "hat.png"), Path.Combine(dgaPath, "assets", "hats", packData.ID + ".png")); } } if (Directory.Exists(Path.Combine(jaPath, "Weapons"))) { Directory.CreateDirectory(Path.Combine(dgaPath, "assets", "melee-weapons")); foreach (string dir_ in Directory.GetDirectories(Path.Combine(jaPath, "Weapons"))) { string dir = Path.GetFileName(dir_); if (!File.Exists(Path.Combine(jaPath, "Weapons", dir, "weapon.json"))) { continue; } Log.Trace("Converting melee weapon " + dir + "..."); var data = cp.ReadJsonFile <WeaponData>(Path.Combine("Weapons", dir, "weapon.json")); var packData = data.ConvertMeleeWeapon(newModId, i18n, weapons, shops); File.Copy(Path.Combine(jaPath, "Weapons", dir, "weapon.png"), Path.Combine(dgaPath, "assets", "melee-weapons", packData.ID + ".png")); } } if (Directory.Exists(Path.Combine(jaPath, "Shirts"))) { Directory.CreateDirectory(Path.Combine(dgaPath, "assets", "shirts")); foreach (string dir_ in Directory.GetDirectories(Path.Combine(jaPath, "Shirts"))) { string dir = Path.GetFileName(dir_); if (!File.Exists(Path.Combine(jaPath, "Shirts", dir, "shirt.json"))) { continue; } Log.Trace("Converting shirt " + dir + "..."); var data = cp.ReadJsonFile <WeaponData>(Path.Combine("Shirts", dir, "shirt.json")); var packData = data.ConvertShirt(newModId, i18n, shirts); File.Copy(Path.Combine(jaPath, "Shirts", dir, "male.png"), Path.Combine(dgaPath, "assets", "shirts", packData.ID + "_male.png")); if (File.Exists(Path.Combine(jaPath, "Shirts", dir, "female.png"))) { File.Copy(Path.Combine(jaPath, "Shirts", dir, "female.png"), Path.Combine(dgaPath, "assets", "shirts", packData.ID + "_female.png")); } if (File.Exists(Path.Combine(jaPath, "Shirts", dir, "male-color.png"))) { File.Copy(Path.Combine(jaPath, "Shirts", dir, "male-color.png"), Path.Combine(dgaPath, "assets", "shirts", packData.ID + "_male_color.png")); } if (File.Exists(Path.Combine(jaPath, "Shirts", dir, "female-color.png"))) { File.Copy(Path.Combine(jaPath, "Shirts", dir, "female-color.png"), Path.Combine(dgaPath, "assets", "shirts", packData.ID + "_female_color.png")); } } } var serializeSettings = new JsonSerializerSettings() { DefaultValueHandling = DefaultValueHandling.Ignore, Formatting = Formatting.Indented, }; if (objs.Count > 0) { File.WriteAllText(Path.Combine(dgaPath, "objects.json"), JsonConvert.SerializeObject(objs, serializeSettings)); } if (crops.Count > 0) { File.WriteAllText(Path.Combine(dgaPath, "crops.json"), JsonConvert.SerializeObject(crops, serializeSettings)); } if (fruitTrees.Count > 0) { File.WriteAllText(Path.Combine(dgaPath, "fruit-trees.json"), JsonConvert.SerializeObject(fruitTrees, serializeSettings)); } if (bigs.Count > 0) { File.WriteAllText(Path.Combine(dgaPath, "big-craftables.json"), JsonConvert.SerializeObject(bigs, serializeSettings)); } if (hats.Count > 0) { File.WriteAllText(Path.Combine(dgaPath, "hats.json"), JsonConvert.SerializeObject(hats, serializeSettings)); } if (weapons.Count > 0) { File.WriteAllText(Path.Combine(dgaPath, "melee-weapons.json"), JsonConvert.SerializeObject(weapons, serializeSettings)); } if (shirts.Count > 0) { File.WriteAllText(Path.Combine(dgaPath, "shirts.json"), JsonConvert.SerializeObject(shirts, serializeSettings)); } if (pants.Count > 0) { File.WriteAllText(Path.Combine(dgaPath, "pants.json"), JsonConvert.SerializeObject(pants, serializeSettings)); } if (tailoring.Count > 0) { File.WriteAllText(Path.Combine(dgaPath, "tailoring-recipes.json"), JsonConvert.SerializeObject(tailoring, serializeSettings)); } if (boots.Count > 0) { File.WriteAllText(Path.Combine(dgaPath, "boots.json"), JsonConvert.SerializeObject(boots, serializeSettings)); } if (fences.Count > 0) { File.WriteAllText(Path.Combine(dgaPath, "fences.json"), JsonConvert.SerializeObject(fences, serializeSettings)); } if (forges.Count > 0) { File.WriteAllText(Path.Combine(dgaPath, "forge-recipes.json"), JsonConvert.SerializeObject(forges, serializeSettings)); } if (crafting.Count > 0) { File.WriteAllText(Path.Combine(dgaPath, "crafting-recipes.json"), JsonConvert.SerializeObject(crafting, serializeSettings)); } if (shops.Count > 0) { File.WriteAllText(Path.Combine(dgaPath, "shop-entries.json"), JsonConvert.SerializeObject(shops, serializeSettings)); } if (giftTastes.Count > 0) { File.WriteAllText(Path.Combine(dgaPath, "gift-tastes.json"), JsonConvert.SerializeObject(giftTastes, serializeSettings)); } Directory.CreateDirectory(Path.Combine(dgaPath, "i18n")); foreach (var entry in i18n) { File.WriteAllText(Path.Combine(dgaPath, "i18n", entry.Key + ".json"), JsonConvert.SerializeObject(entry.Value, serializeSettings)); } var manifest = new Manifest(); manifest.Name = cp.Manifest.Name + " (DGA version)"; manifest.Description = cp.Manifest.Description; manifest.Author = cp.Manifest.Author; manifest.Version = cp.Manifest.Version; manifest.MinimumApiVersion = cp.Manifest.MinimumApiVersion; manifest.UniqueID = cp.Manifest.UniqueID + ".DGA"; manifest.ContentPackFor = new ManifestContentPackFor() { UniqueID = "spacechase0.DynamicGameAssets" }; manifest.Dependencies = cp.Manifest.Dependencies; manifest.UpdateKeys = cp.Manifest.UpdateKeys; manifest.ExtraFields = cp.Manifest.ExtraFields ?? new Dictionary <string, object>(); manifest.ExtraFields.Add("DGA.FormatVersion", 1); manifest.ExtraFields.Add("DGA.ConditionsFormatVersion", "1.23.0"); File.WriteAllText(Path.Combine(dgaPath, "manifest.json"), JsonConvert.SerializeObject(manifest, serializeSettings)); var dga = this.Helper.ModRegistry.GetApi <IDynamicGameAssetsApi>("spacechase0.DynamicGameAssets"); dga.AddEmbeddedPack(manifest, dgaPath); Log.Info("Done!"); Log.Info("We did some black magic to go ahead and load it without restarting the game, too. :)"); Log.Info("Please do not upload converted packs for mods that you don't have permission to do!"); Log.Info("NOTE: Regrowing crops work differently in DGA than in JA! See the making content packs documentation for detail."); Log.Info("NOTE: Crafting recipes are incomplete if they used ingredients from a different pack! They need prefixing with \"pack.id/\". (See documentation for details.)"); }
/// <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 }
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>() }; // TODO: Clean up this chunk // I copy/pasted it from the unofficial update decompiled string str = ""; string[] array = new string[] { "spring", "summer", "fall", "winter" }.Except(crop.Seasons).ToArray <string>(); for (int i = 0; i < array.Length; i++) { string season = array[i]; str += string.Format("/z {0}", 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(string.Format(" Faulty season requirements for {0}!\n", crop.SeedName) + string.Format(" Fixed season requirements: {0}", crop.SeedPurchaseRequirements[index])); } } if (!crop.SeedPurchaseRequirements.Contains(str.TrimStart(new char[] { '/' }))) { Log.trace(string.Format(" Adding season requirements for {0}:\n", crop.SeedName) + string.Format(" New season requirements: {0}", strtrimstart)); crop.seed.PurchaseRequirements.Add(strtrimstart); } } else { Log.trace(string.Format(" Adding season requirements for {0}:\n", crop.SeedName) + string.Format(" New season requirements: {0}", 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 }; 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 objects 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); } } }
/// <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; 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); }
public OverlayDefinition GetDefinition() { string ovjson = Path.Combine(AssetDir, "overlay.json"); return(ContentPack?.ReadJsonFile <OverlayDefinition>(ovjson) ?? Helper.Data.ReadJsonFile <OverlayDefinition>(ovjson)); }