Modding Help [SMAPI] Modding Spouse Rooms

Discussion in 'Mods' started by sivolobwho, May 17, 2016.

  1. sivolobwho

    sivolobwho Phantasmal Quasar

    As much as I wanted to figure this out myself, it has come to this. I'm kind of new to programming, but I catch on pretty quickly, so I thought it'd be doable to try out an API mod on my own.

    Basically, what I am trying to do is pretty simple (or at least I think it should be). I'm attempting to make an API mod to go along with my marriage mod (an XNB mod) that will add spouse rooms for the nine (soon to be eleven) additional marriage candidates that my current mod adds. The same way the base game has spouse rooms for the default candidates.

    I started off testing this spouse room mod to see if I could make it work with my marriage mod. It worked okay, but there were a few weird problems with the mod itself, and when I tried to fix some problems with the mod's code myself, I couldn't recompile it (there were too many errors that I didn't create - things in the code that neither myself nor Google understood). So eventually I gave up on that mod and decided to try making my own, since I'm pretty sure Watagatapitusberry is no longer active on here.

    Here I am, a week later and I haven't had any success with my mod. I can get errors to pop up in the API... which means I'm doing something right, I hope. But as far as making the actual mod work, nothing I've tried is effective. It's just showing the same, blank rectangle where the room should be.

    I've edited and repacked the spouseRooms.xnb file, so that's totally fine... Even added some custom tiles in the townInterior.xnb file (which I confirmed as working with Watagatapitusberry's mod linked above). I've studied how the game's code loads the spouse rooms, as well as how Watagatapitusberry's mod does it, and I simply don't understand why mine won't work. (I have elements from both places used in my code.)

    Code:
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Content;
    using StardewModdingAPI;
    using StardewModdingAPI.Events;
    using StardewValley;
    using StardewValley.Locations;
    using System;
    using System.Collections.Generic;
    using System.Text;
    using xTile;
    using xTile.ObjectModel;
    using xTile.Tiles;
    
    namespace SivsSpouseRooms
    {
        public class SivsSpouseRooms : Mod
        {
            private static bool spouseRoomLoaded;
            public Map map;
    
            public override void Entry(params object[] objects)
            {
                    GameEvents.UpdateTick += new EventHandler(Events_UpdateTick);
            }
    
            private void Events_UpdateTick(object sender, EventArgs e)
            {
                LoadSpouseRoom();
            }
    
            public static void LoadSpouseRoom()
            {
               
    NPC npc = ((Farmer)Game1.player).getSpouse();
                if (npc == null)
                    return;
                int num = -1;
                string spouse = npc.name;
                if (spouse == "Gus")
                {
                    num = 11;
                }
    
                    int houseUpgradeLevel = ((Farmer)Game1.player).HouseUpgradeLevel;
                    Rectangle rectangle = houseUpgradeLevel == 1 ? new Rectangle(29, 1, 6, 9) : new Rectangle(35, 10, 6, 9);
                    Map map = Game1.content.Load<Map>("Maps\\spouseRooms");
                    Point point = new Point(num % 5 * 6, num / 5 * 9);
                    map.Properties.Remove("DayTiles");
                    map.Properties.Remove("NightTiles");
    
                for (int index1 = 0; index1 < rectangle.Width; ++index1)
                    {
                        for (int index2 = 0; index2 < rectangle.Height; ++index2)
                        {
                            if (map.GetLayer("Back").Tiles[point.X + index1, point.Y + index2] != null)
                                map.GetLayer("Back").Tiles[rectangle.X + index1, rectangle.Y + index2] = (Tile)new StaticTile(map.GetLayer("Back"), map.TileSheets[0], BlendMode.Alpha, map.GetLayer("Back").Tiles[point.X + index1, point.Y + index2].TileIndex);
                            if (map.GetLayer("Buildings").Tiles[point.X + index1, point.Y + index2] != null)
                            {
                                map.GetLayer("Buildings").Tiles[rectangle.X + index1, rectangle.Y + index2] = (Tile)new StaticTile(map.GetLayer("Buildings"), map.TileSheets[0], BlendMode.Alpha, map.GetLayer("Buildings").Tiles[point.X + index1, point.Y + index2].TileIndex);
                                adjustMapLightPropertiesForLamp(map.GetLayer("Buildings").Tiles[point.X + index1, point.Y + index2].TileIndex, rectangle.X + index1, rectangle.Y + index2, "Buildings");
                            }
                            else
                                map.GetLayer("Buildings").Tiles[rectangle.X + index1, rectangle.Y + index2] = (Tile)null;
                            if (index2 < rectangle.Height - 1 && map.GetLayer("Front").Tiles[point.X + index1, point.Y + index2] != null)
                            {
                                map.GetLayer("Front").Tiles[rectangle.X + index1, rectangle.Y + index2] = (Tile)new StaticTile(map.GetLayer("Front"), map.TileSheets[0], BlendMode.Alpha, map.GetLayer("Front").Tiles[point.X + index1, point.Y + index2].TileIndex);
                                adjustMapLightPropertiesForLamp(map.GetLayer("Front").Tiles[point.X + index1, point.Y + index2].TileIndex, rectangle.X + index1, rectangle.Y + index2, "Front");
                            }
                            else if (index2 < rectangle.Height - 1)
                                map.GetLayer("Front").Tiles[rectangle.X + index1, rectangle.Y + index2] = (Tile)null;
                            if (index1 == 4 && index2 == 4)
                                map.GetLayer("Back").Tiles[rectangle.X + index1, rectangle.Y + index2].Properties.Add(new KeyValuePair<string, PropertyValue>("NoFurniture", new PropertyValue("T")));
                        }
                    }
                spouseRoomLoaded = true;
            }
            public static void adjustMapLightPropertiesForLamp(int tile, int x, int y, string layer)
            {
                switch (tile)
                {
                    case 480:
                        SivsSpouseRooms.changeMapProperties("DayTiles", layer + " " + (object)x + " " + (object)y + " " + (object)tile);
                        SivsSpouseRooms.changeMapProperties("NightTiles", layer + " " + (object)x + " " + (object)y + " " + (object)809);
                        SivsSpouseRooms.changeMapProperties("Light", x.ToString() + " " + (object)y + " 4");
                        break;
                    case 826:
                        SivsSpouseRooms.changeMapProperties("DayTiles", layer + " " + (object)x + " " + (object)y + " " + (object)tile);
                        SivsSpouseRooms.changeMapProperties("NightTiles", layer + " " + (object)x + " " + (object)y + " " + (object)827);
                        SivsSpouseRooms.changeMapProperties("Light", x.ToString() + " " + (object)y + " 4");
                        break;
                    case 1344:
                        SivsSpouseRooms.changeMapProperties("DayTiles", layer + " " + (object)x + " " + (object)y + " " + (object)tile);
                        SivsSpouseRooms.changeMapProperties("NightTiles", layer + " " + (object)x + " " + (object)y + " " + (object)1345);
                        SivsSpouseRooms.changeMapProperties("Light", x.ToString() + " " + (object)y + " 4");
                        break;
                    case 1346:
                        SivsSpouseRooms.changeMapProperties("DayTiles", "Front " + (object)x + " " + (object)y + " " + (object)tile);
                        SivsSpouseRooms.changeMapProperties("NightTiles", "Front " + (object)x + " " + (object)y + " " + (object)1347);
                        SivsSpouseRooms.changeMapProperties("DayTiles", "Buildings " + (object)x + " " + (object)(y + 1) + " " + (object)452);
                        SivsSpouseRooms.changeMapProperties("NightTiles", "Buildings " + (object)x + " " + (object)(y + 1) + " " + (object)453);
                        SivsSpouseRooms.changeMapProperties("Light", x.ToString() + " " + (object)y + " 4");
                        break;
                    case 8:
                        SivsSpouseRooms.changeMapProperties("Light", x.ToString() + " " + (object)y + " 9");
                        break;
                    case 225:
                        if (((string)Game1.getLocationFromName("FarmHouse").name).Contains("BathHouse"))
                            break;
                        SivsSpouseRooms.changeMapProperties("DayTiles", layer + " " + (object)x + " " + (object)y + " " + (object)tile);
                        SivsSpouseRooms.changeMapProperties("NightTiles", layer + " " + (object)x + " " + (object)y + " " + (object)1222);
                        SivsSpouseRooms.changeMapProperties("DayTiles", layer + " " + (object)x + " " + (object)(y + 1) + " " + (object)257);
                        SivsSpouseRooms.changeMapProperties("NightTiles", layer + " " + (object)x + " " + (object)(y + 1) + " " + (object)1254);
                        SivsSpouseRooms.changeMapProperties("Light", x.ToString() + " " + (object)y + " 4");
                        SivsSpouseRooms.changeMapProperties("Light", x.ToString() + " " + (object)(y + 1) + " 4");
                        break;
                    case 256:
                        SivsSpouseRooms.changeMapProperties("DayTiles", layer + " " + (object)x + " " + (object)y + " " + (object)tile);
                        SivsSpouseRooms.changeMapProperties("NightTiles", layer + " " + (object)x + " " + (object)y + " " + (object)1253);
                        SivsSpouseRooms.changeMapProperties("DayTiles", layer + " " + (object)x + " " + (object)(y + 1) + " " + (object)288);
                        SivsSpouseRooms.changeMapProperties("NightTiles", layer + " " + (object)x + " " + (object)(y + 1) + " " + (object)1285);
                        SivsSpouseRooms.changeMapProperties("Light", x.ToString() + " " + (object)y + " 4");
                        SivsSpouseRooms.changeMapProperties("Light", x.ToString() + " " + (object)(y + 1) + " 4");
                        break;
                }
            }
    
            private static void changeMapProperties(string propertyName, string toAdd)
            {
                try
                {
                    bool flag = true;
                    if (!((IDictionary<string, PropertyValue>)((Component)Game1.getLocationFromName("FarmHouse").map).Properties).ContainsKey(propertyName))
                    {
                        ((IDictionary<string, PropertyValue>)((Component)Game1.getLocationFromName("FarmHouse").map).Properties).Add(propertyName, new PropertyValue(string.Empty));
                        flag = false;
                    }
                    if (((object)((IDictionary<string, PropertyValue>)((Component)Game1.getLocationFromName("FarmHouse").map).Properties)[propertyName]).ToString().Contains(toAdd))
                        return;
                    StringBuilder stringBuilder = new StringBuilder(((object)((IDictionary<string, PropertyValue>)((Component)Game1.getLocationFromName("FarmHouse").map).Properties)[propertyName]).ToString());
                    if (flag)
                        stringBuilder.Append(" ");
                    stringBuilder.Append(toAdd);
                    ((IDictionary<string, PropertyValue>)((Component)Game1.getLocationFromName("FarmHouse").map).Properties)[propertyName] = new PropertyValue(stringBuilder.ToString());
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.ToString());
                }
            }
        }
    }


    As of right now, I've only been testing with Gus' spouse room, which is 11. This code gives me the following error logged in the API (repeated over and over):


    [04:54:18.235 PM] An exception occured in XNA UpdateTick: System.IndexOutOfRangeException: Index was outside the bounds of the array.
    at xTile.Tiles.TileArray.set_Item(Int32 x, Int32 y, Tile value)
    at SivsSpouseRooms.SivsSpouseRooms.LoadSpouseRoom()
    at SivsSpouseRooms.SivsSpouseRooms.Events_UpdateTick(Object sender, EventArgs e)
    at System.EventHandler.Invoke(Object sender, EventArgs e)
    at StardewModdingAPI.Events.GameEvents.InvokeUpdateTick()


    I'm at a loss. It's probably because I'm still learning this C# business. If there's something majorly wrong with how I have things set up, please let me know... From what little I know, and my common sense, it looks like it should work to me. :^(

    Any help or advice would be incredible! And please be kind, I'm still learning...
     
    • Jinxiewinxie

      Jinxiewinxie Farmer Fashionista

      Pointing Advize to this thread and also noting that so he's obligated to give you a good answer =)
       
        Minakie likes this.
      • Advize

        Advize Cosmic Narwhal

        I hate you.

        I'll be completely honest and tell you that I didn't read even a tenth of your code. I will say this, however. You subscribe to GameEvents.UpdateTick, which (btw) ticks very frequently (even before the game has loaded) and then never unsubscribe from it. Basically you're executing almost all of your code repeatedly multiple times per second. Ideally, you'd want your code only to be executed once and at the appropriate time (once everything has loaded).

        Your error is a common one, System.IndexOutOfRangeException. This means that you are trying to access/operate on an item within a list or array that is beyond the range of the array. If I have an array of 5 items (indexes 0, 1, 2, 3, and 4), and I try to access the 5th index or above, I'd get an IndexOutOfRangeException.

        A quick glance at your code shows several glaring errors. If you want further support, I strongly recommend coming on to the Stardew Modding Discord channel. You can either download the Discord app for your phone, your Desktop, or even use it in your browser. Jinxie can provide you with an invite to it.
         
        • Jinxiewinxie

          Jinxiewinxie Farmer Fashionista

          Sent you a message with a link to access Discord.
           
          • Advize

            Advize Cosmic Narwhal

            I'm guessing most of your code was obtained by decompiling the mod you spoke of. As a result, it was full of redundant casts. I removed those, fixed a couple things that looked weird with your for loops, and also set it so that LoadSpouseRoom() is only ever called if spouseRoomLoaded is false.

            Code:
            using System;
            using System.Collections.Generic;
            using System.Text;
            
            using StardewValley;
            using StardewModdingAPI;
            using StardewModdingAPI.Events;
            
            using Microsoft.Xna.Framework;
            using xTile;
            using xTile.ObjectModel;
            using xTile.Tiles;
            
            namespace SivsSpouseRooms
            {
                public class SivsSpouseRooms : Mod
                {
                    private static bool spouseRoomLoaded;
                    public Map map;
            
                    public override void Entry(params object[] objects)
                    {
                        GameEvents.UpdateTick += Events_UpdateTick;
                    }
            
                    private void Events_UpdateTick(object sender, EventArgs e)
                    {
                        if (!Game1.hasLoadedGame)
                            return;
                        if (!spouseRoomLoaded)
                            LoadSpouseRoom();
                    }
            
                    public static void LoadSpouseRoom()
                    {
                        NPC npc = Game1.player.getSpouse();
                        if (npc == null)
                            return;
            
                        int num = -1;
                        string spouse = npc.name;
                        if (spouse == "Gus")
                        {
                            num = 11;
                        }
            
                        int houseUpgradeLevel = Game1.player.HouseUpgradeLevel;
                        Rectangle rectangle = houseUpgradeLevel == 1 ? new Rectangle(29, 1, 6, 9) : new Rectangle(35, 10, 6, 9);
                        Map map = Game1.content.Load<Map>("Maps\\spouseRooms");
                        Point point = new Point(num % 5 * 6, num / 5 * 9);
                        map.Properties.Remove("DayTiles");
                        map.Properties.Remove("NightTiles");
            
                        for (int index1 = 0; index1 < rectangle.Width; index1++)
                        {
                            for (int index2 = 0; index2 < rectangle.Height; index2++)
                            {
                                if (map.GetLayer("Back").Tiles[point.X + index1, point.Y + index2] != null)
                                    map.GetLayer("Back").Tiles[rectangle.X + index1, rectangle.Y + index2] = new StaticTile(map.GetLayer("Back"), map.TileSheets[0], BlendMode.Alpha, map.GetLayer("Back").Tiles[point.X + index1, point.Y + index2].TileIndex);
                                if (map.GetLayer("Buildings").Tiles[point.X + index1, point.Y + index2] != null)
                                {
                                    map.GetLayer("Buildings").Tiles[rectangle.X + index1, rectangle.Y + index2] = new StaticTile(map.GetLayer("Buildings"), map.TileSheets[0], BlendMode.Alpha, map.GetLayer("Buildings").Tiles[point.X + index1, point.Y + index2].TileIndex);
                                    adjustMapLightPropertiesForLamp(map.GetLayer("Buildings").Tiles[point.X + index1, point.Y + index2].TileIndex, rectangle.X + index1, rectangle.Y + index2, "Buildings");
                                }
                                else
                                    map.GetLayer("Buildings").Tiles[rectangle.X + index1, rectangle.Y + index2] = null;
                                if (index2 < rectangle.Height - 1 && map.GetLayer("Front").Tiles[point.X + index1, point.Y + index2] != null)
                                {
                                    map.GetLayer("Front").Tiles[rectangle.X + index1, rectangle.Y + index2] = new StaticTile(map.GetLayer("Front"), map.TileSheets[0], BlendMode.Alpha, map.GetLayer("Front").Tiles[point.X + index1, point.Y + index2].TileIndex);
                                    adjustMapLightPropertiesForLamp(map.GetLayer("Front").Tiles[point.X + index1, point.Y + index2].TileIndex, rectangle.X + index1, rectangle.Y + index2, "Front");
                                }
                                else if (index2 < rectangle.Height - 1)
                                    map.GetLayer("Front").Tiles[rectangle.X + index1, rectangle.Y + index2] = null;
                                if (index1 == 4 && index2 == 4)
                                    map.GetLayer("Back").Tiles[rectangle.X + index1, rectangle.Y + index2].Properties.Add(new KeyValuePair<string, PropertyValue>("NoFurniture", new PropertyValue("T")));
                            }
                        }
                        spouseRoomLoaded = true;
                    }
            
                    public static void adjustMapLightPropertiesForLamp(int tile, int x, int y, string layer)
                    {
                        switch (tile)
                        {
                            case 480:
                                changeMapProperties("DayTiles", layer + " " + x + " " + y + " " + tile);
                                changeMapProperties("NightTiles", layer + " " + x + " " + y + " " + 809);
                                changeMapProperties("Light", x.ToString() + " " + y + " 4");
                                break;
                            case 826:
                                changeMapProperties("DayTiles", layer + " " + x + " " + y + " " + tile);
                                changeMapProperties("NightTiles", layer + " " + x + " " + y + " " + 827);
                                changeMapProperties("Light", x.ToString() + " " + y + " 4");
                                break;
                            case 1344:
                                changeMapProperties("DayTiles", layer + " " + x + " " + y + " " + tile);
                                changeMapProperties("NightTiles", layer + " " + x + " " + y + " " + 1345);
                                changeMapProperties("Light", x.ToString() + " " + y + " 4");
                                break;
                            case 1346:
                                changeMapProperties("DayTiles", "Front " + x + " " + y + " " + tile);
                                changeMapProperties("NightTiles", "Front " + x + " " + y + " " + 1347);
                                changeMapProperties("DayTiles", "Buildings " + x + " " + (y + 1) + " " + 452);
                                changeMapProperties("NightTiles", "Buildings " + x + " " + (y + 1) + " " + 453);
                                changeMapProperties("Light", x.ToString() + " " + y + " 4");
                                break;
                            case 8:
                                changeMapProperties("Light", x.ToString() + " " + y + " 9");
                                break;
                            case 225:
                                if ((Game1.getLocationFromName("FarmHouse").name).Contains("BathHouse")) // This is impossible, the name is FarmHouse which clearly does not contain BathHouse
                                    break;
                                changeMapProperties("DayTiles", layer + " " + x + " " + y + " " + tile);
                                changeMapProperties("NightTiles", layer + " " + x + " " + y + " " + 1222);
                                changeMapProperties("DayTiles", layer + " " + x + " " + (y + 1) + " " + 257);
                                changeMapProperties("NightTiles", layer + " " + x + " " + (y + 1) + " " + 1254);
                                changeMapProperties("Light", x.ToString() + " " + y + " 4");
                                changeMapProperties("Light", x.ToString() + " " + (y + 1) + " 4");
                                break;
                            case 256:
                                changeMapProperties("DayTiles", layer + " " + x + " " + y + " " + tile);
                                changeMapProperties("NightTiles", layer + " " + x + " " + y + " " + 1253);
                                changeMapProperties("DayTiles", layer + " " + x + " " + (y + 1) + " " + 288);
                                changeMapProperties("NightTiles", layer + " " + x + " " + (y + 1) + " " + 1285);
                                changeMapProperties("Light", x.ToString() + " " + y + " 4");
                                changeMapProperties("Light", x.ToString() + " " + (y + 1) + " 4");
                                break;
                        }
                    }
            
                    private static void changeMapProperties(string propertyName, string toAdd)
                    {
                        try
                        {
                            bool flag = true;
                            if (!Game1.getLocationFromName("FarmHouse").map.Properties.ContainsKey(propertyName))
                            {
                                Game1.getLocationFromName("FarmHouse").map.Properties.Add(propertyName, new PropertyValue(string.Empty));
                                flag = false;
                            }
                            if (Game1.getLocationFromName("FarmHouse").map.Properties[propertyName].ToString().Contains(toAdd))
                                return;
                            StringBuilder stringBuilder = new StringBuilder(Game1.getLocationFromName("FarmHouse").map.Properties[propertyName].ToString());
                            if (flag)
                                stringBuilder.Append(" ");
                            stringBuilder.Append(toAdd);
                            Game1.getLocationFromName("FarmHouse").map.Properties[propertyName] = new PropertyValue(stringBuilder.ToString());
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine(ex.ToString());
                        }
                    }
                }
            }
            
            I haven't compiled or tested this. My guess is that your index out of range exception is coming from your nested for loops where you iterate through each x and y value in your rectangle.
             
              Jinxiewinxie likes this.
            • sivolobwho

              sivolobwho Phantasmal Quasar

              I will take a look at the for loops then and try to find what's wrong with them. Thank you so so much, that is such a big help. I will post in the Discord chat if I have any specific questions. Again, thank you so much for taking the time to look at my code! Like I said, I'm very much a beginner with C# (I literally just started learning it for modding SDV) so I'm not surprised there are lots of errors. ^^;
               
                Jinxiewinxie likes this.
              • juicyslew

                juicyslew Master Chief

                Is the discord channel for everyone? if so can you link me? I am trying to understand how to use SMAPI and could use support / help
                 
                • Jinxiewinxie

                  Jinxiewinxie Farmer Fashionista

                Share This Page