/// <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);
     }
 }
Beispiel #2
0
            /// <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);
            }
Beispiel #3
0
            /// <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);
                }
            }