/// <summary>Attempts to generate and place an item using the <see cref="Utility.DGAItemAPI"/> interface.</summary> /// <param name="forage">The SavedObject containing this forage's information.</param> /// <param name="location">The GameLocation where the forage should be spawned.</param> /// <param name="tile">The x/y coordinates of the tile where the ore should be spawned.</param> /// <returns>True if the item spawned successfully; false otherwise.</returns> private static bool SpawnDGAItem(SavedObject forage, GameLocation location, Vector2 tile) { try { object rawDGA = DGAItemAPI.SpawnDGAItem(forage.Name); //try to create this item with DGA's API if (rawDGA is Furniture furnitureDGA) //if the resulting item is furniture { Monitor.VerboseLog($"Spawning DGA forage furniture. Name: {forage.Name}. Location: {tile.X},{tile.Y} ({location.Name})."); furnitureDGA.TileLocation = tile; Rectangle originalBoundingBox = furnitureDGA.boundingBox.Value; //get "original" bounding box furnitureDGA.boundingBox.Value = new Rectangle((int)tile.X * 64, (int)tile.Y * 64, originalBoundingBox.Width, originalBoundingBox.Height); //adjust for tile position furnitureDGA.updateDrawPosition(); location.furniture.Add(furnitureDGA); //add the furniture to this location return(true); } else if (rawDGA is StardewValley.Object objectDGA) //if the resulting item is a SDV object (i.e. can be treated like normal forage) { Monitor.VerboseLog($"Spawning DGA forage object. Name: {forage.Name}. Location: {tile.X},{tile.Y} ({location.Name})."); objectDGA.IsSpawnedObject = true; return(location.dropObject(objectDGA, tile * 64f, Game1.viewport, true, null)); //attempt to place the object and return success/failure } else if (rawDGA is Item itemDGA) //if the resulting item is any other type of Item (i.e. can be treated as a PlacedItem) { if (location.terrainFeatures.ContainsKey(tile)) //if a terrain feature already exists on this tile { return(false); //fail to spawn } Monitor.VerboseLog($"Spawning DGA forage item. Name: {forage.Name}. Location: {tile.X},{tile.Y} ({location.Name})."); PlacedItem placed = new PlacedItem(tile, itemDGA); //create a terrainfeature containing the item location.terrainFeatures.Add(tile, placed); //add the placed item to this location return(true); } else if (rawDGA != null) //if DGA spawned an item, but it isn't a recognized type { Monitor.Log("Dynamic Game Assets (DGA) created an item, but FTM doesn't recognize its type. This may be caused by the item or a problem with FTM's logic.", LogLevel.Warn); Monitor.Log($"Item name: {forage.Name}", LogLevel.Warn); Monitor.Log($"Item type (C# code): {rawDGA.GetType()?.Name ?? "null"}", LogLevel.Warn); return(false); } else //if DGA did not spawn an item { Monitor.Log("The SpawnForage method failed to generate a Dynamic Game Assets (DGA) item. This may be caused by a problem with this mod's logic. Please report this to FTM's developer if possible.", LogLevel.Warn); Monitor.Log($"Item name: {forage.Name}", LogLevel.Warn); return(false); } } catch (Exception ex) { Monitor.Log($"An error occurred while spawning a Dynamic Game Assets (DGA) item.", LogLevel.Warn); Monitor.Log($"Item name: \"{forage.Name}\"", LogLevel.Warn); Monitor.Log($"The affected item will be skipped. The auto-generated error message has been added to the log.", LogLevel.Warn); Monitor.Log($"----------", LogLevel.Trace); Monitor.Log($"{ex.ToString()}", LogLevel.Trace); return(false); } }
/// <summary>Generates an item described by a saved object.</summary> /// <param name="save">A saved object descibing an item.</param> /// <param name="tile">The object's intended tile location. Generally necessary for items derived from StardewValley.Object.</param> public static Item CreateItem(SavedObject save, Vector2 tile = default(Vector2)) { switch (save.Type) //check the object's type { case SavedObject.ObjectType.Object: case SavedObject.ObjectType.Item: case SavedObject.ObjectType.Container: case SavedObject.ObjectType.DGA: //these are valid item types break; default: Monitor.Log($"Failed to create an item. Saved object does not appear to be an item.", LogLevel.Debug); Monitor.Log($"Item name: {save.Name}", LogLevel.Debug); return(null); } if (!save.ID.HasValue && save.Type != SavedObject.ObjectType.Container && save.Type != SavedObject.ObjectType.DGA) //if this save doesn't have an ID (and isn't a container or a DGA item) { Monitor.Log("Failed to create an item. Saved object contained no ID.", LogLevel.Debug); Monitor.Log($"Item name: {save.Name}", LogLevel.Debug); return(null); } Item item = null; //the item to be generated ConfigItem configItem = save.ConfigItem; //the ConfigItem class describing the item (null if unavailable) //parse container contents, if applicable List <Item> contents = new List <Item>(); if (save.Type == SavedObject.ObjectType.Container) //if this is a container { string areaID = $"[unknown; parsing chest contents at {save.MapName}]"; //placeholder string; this method has no easy access to the areaID that created a given item List <SavedObject> contentSaves = ParseSavedObjectsFromItemList(configItem.Contents, areaID); //parse the contents into saved objects for validation purposes foreach (SavedObject contentSave in contentSaves) //for each successfully parsed save { Item content = CreateItem(contentSave); //call this method recursively to create this item if (content != null) //if this item was created successfully { contents.Add(content); //add it to the contents list } } } string category = "item"; if (configItem != null && configItem.Category != null) { category = configItem.Category.ToLower(); } switch (category) //based on the category { case "barrel": case "barrels": item = new BreakableContainerFTM(tile, contents, true); //create a mineshaft-style breakable barrel with the given contents break; case "bigcraftable": case "bigcraftables": case "big craftable": case "big craftables": item = new StardewValley.Object(tile, save.ID.Value, false); //create an object as a "big craftable" item break; case "boot": case "boots": item = new Boots(save.ID.Value); break; case "breakable": case "breakables": bool barrel = RNG.Next(0, 2) == 0 ? true : false; //randomly select whether this is a barrel or crate if (configItem != null) { //rewrite the category to save the selection if (barrel) { configItem.Category = "barrel"; } else { configItem.Category = "crate"; } } item = new BreakableContainerFTM(tile, contents, barrel); //create a mineshaft-style breakable container with the given contents break; case "buried": case "burieditem": case "burieditems": case "buried item": case "buried items": item = new BuriedItems(tile, contents); //create an item burial location with the given contents break; case "chest": case "chests": item = new Chest(0, contents, tile, false, 0); //create a mineshaft-style chest with the given contents break; case "cloth": case "clothes": case "clothing": case "clothings": item = new Clothing(save.ID.Value); break; case "crate": case "crates": item = new BreakableContainerFTM(tile, contents, false); //create a mineshaft-style breakable crate with the given contents break; case "dga": try { object rawDGA = DGAItemAPI.SpawnDGAItem(save.Name); //create an item with DGA's API if (rawDGA is Item itemDGA) //if this is a non-null Item { item = itemDGA; //use it } else { Monitor.Log("Failed to create an item. Dynamic Game Assets (DGA) item was null or an unrecognized type.", LogLevel.Debug); Monitor.Log($"Item name: {save.Name}", LogLevel.Debug); return(null); } } catch (Exception ex) { Monitor.LogOnce($"Error spawning a Dynamic Game Assets (DGA) item. The auto-generated error message has been added to the log.", LogLevel.Info); Monitor.Log($"----------", LogLevel.Trace); Monitor.Log($"{ex.ToString()}", LogLevel.Trace); return(null); } break; case "furniture": item = new Furniture(save.ID.Value, tile); break; case "hat": case "hats": item = new Hat(save.ID.Value); break; case "object": //treat objects as items when creating them as Items case "objects": case "item": case "items": item = new StardewValley.Object(tile, save.ID.Value, 1); //create an object with the preferred constructor for "held" or "dropped" items break; case "ring": case "rings": item = new Ring(save.ID.Value); break; case "weapon": case "weapons": item = new MeleeWeapon(save.ID.Value); break; } if (item == null) //if no item could be generated { Monitor.Log("Failed to create an item. Category setting was not recognized.", LogLevel.Debug); Monitor.Log($"Item Category: {category}", LogLevel.Debug); return(null); } if (configItem?.Stack > 1) //if this item has a custom stack setting { item.Stack = configItem.Stack.Value; //apply it } if (save.ID.HasValue) //if this object type uses an ID { item.ParentSheetIndex = save.ID.Value; //manually set this index value, due to it being ignored by some item subclasses } return(item); }
/// <summary>Uses a ConfigItem to create a saved object representing an item.</summary> /// <param name="item">The ConfigItem class describing the item.</param> /// <param name="areaID">The UniqueAreaID of the related SpawnArea. Required for log messages.</param> /// <returns>A saved object representing the designated item. Null if creation failed.</returns> private static SavedObject CreateSavedObject(ConfigItem item, string areaID = "") { switch (item.Type) { case SavedObject.ObjectType.Object: case SavedObject.ObjectType.Item: case SavedObject.ObjectType.Container: case SavedObject.ObjectType.DGA: //these are valid item types break; default: Monitor.Log($"An area's item list contains a complex item with a type that is not recognized.", LogLevel.Info); Monitor.Log($"Affected spawn area: \"{areaID}\"", LogLevel.Info); Monitor.Log($"Item type: \"{item.Type}\"", LogLevel.Info); Monitor.Log($"This is likely due to a design error in the mod's code. Please report this to the mod's developer. The affected item will be skipped.", LogLevel.Info); return(null); } if (item.Contents != null) //if this item has contents { for (int x = item.Contents.Count - 1; x >= 0; x--) //for each of the contents { List <SavedObject> contentSave = ParseSavedObjectsFromItemList(new object[] { item.Contents[x] }, areaID); //attempt to parse this into a saved object if (contentSave.Count <= 0) //if parsing failed { item.Contents.RemoveAt(x); //remove this from the contents list } } } if (item.Type == SavedObject.ObjectType.Container) //if this is a container { //containers have no name or ID to validate, so don't involve them SavedObject saved = new SavedObject() //generate a saved object with these settings { Type = item.Type, ConfigItem = item }; Monitor.VerboseLog($"Parsed \"{item.Category}\" as a container type."); return(saved); } if (item.Type == SavedObject.ObjectType.DGA) //if this is a DGA item { if (DGAItemAPI != null) //if DGA's API is loaded { try { object testItem = DGAItemAPI.SpawnDGAItem(item.Name); //confirm that this item can be created if (testItem != null) //if this item was created successfully { if (testItem is Item) //if the item is an Item or any subclass of it (SDV object, etc) { SavedObject saved = new SavedObject() //generate a saved object with these settings { Type = item.Type, Name = item.Name, ConfigItem = item }; Monitor.VerboseLog($"Parsed \"{item.Name}\" as a DGA item."); return(saved); } else //if this item not an Item { Monitor.Log($"An area's item list contains a Dynamic Game Assets (DGA) item of a type that FTM does not recognize.", LogLevel.Info); Monitor.Log($"Affected spawn area: \"{areaID}\"", LogLevel.Info); Monitor.Log($"Item name: \"{item.Name}\"", LogLevel.Info); Monitor.Log($"This may be caused by an error in the item list or a type of custom item that FTM cannot spawn. The affected item will be skipped.", LogLevel.Info); return(null); } } else { Monitor.Log($"An area's item list contains a Dynamic Game Assets (DGA) item name that does not match any loaded DGA items.", LogLevel.Info); Monitor.Log($"Affected spawn area: \"{areaID}\"", LogLevel.Info); Monitor.Log($"Item name: \"{item.Name}\"", LogLevel.Info); Monitor.Log($"This may be caused by an error in the item list or a modded object that wasn't loaded. The affected item will be skipped.", LogLevel.Info); return(null); } } catch (Exception ex) { Monitor.Log($"An area's item list contains a Dynamic Game Assets (DGA) item, but an error occurred while test-spawning the item.", LogLevel.Info); Monitor.Log($"Affected spawn area: \"{areaID}\"", LogLevel.Info); Monitor.Log($"Item name: \"{item.Name}\"", LogLevel.Info); Monitor.Log($"The affected item will be skipped. The auto-generated error message has been added to the log.", LogLevel.Info); Monitor.Log($"----------", LogLevel.Trace); Monitor.Log($"{ex.ToString()}", LogLevel.Trace); return(null); } } else //if DGA's API is unavailable { Monitor.Log($"An area's item list contains a Dynamic Game Assets (DGA) item, but that mod's interface is unavailable.", LogLevel.Info); Monitor.Log($"Affected spawn area: \"{areaID}\"", LogLevel.Info); Monitor.Log($"Item name: \"{item.Name}\"", LogLevel.Info); Monitor.Log($"If DGA is not installed, please install it. If FTM displayed an error about DGA's interface, please report this to FTM's developer. The affected item will be skipped.", LogLevel.Info); return(null); } } string savedName = item.Category + ":" + item.Name; int?itemID = GetItemID(item.Category, item.Name); //get an item ID for the category and name if (itemID.HasValue) //if a matching item ID was found { SavedObject saved = new SavedObject() //generate a saved object with these settings { Type = item.Type, Name = savedName, ID = itemID.Value, ConfigItem = item }; Monitor.VerboseLog($"Parsed \"{item.Category}\": \"{item.Name}\" into item ID: {itemID}"); return(saved); } else //if no matching item ID was found { Monitor.Log($"An area's item list contains a complex item definition that did not match any loaded items.", LogLevel.Info); Monitor.Log($"Affected spawn area: \"{areaID}\"", LogLevel.Info); Monitor.Log($"Item name: \"{savedName}\"", LogLevel.Info); Monitor.Log($"This may be caused by an error in the item list or a modded item that wasn't loaded. The affected item will be skipped.", LogLevel.Info); return(null); } }