SMAPI coding help

Discussion in 'Mods' started by BlindBandit, Jan 8, 2017.

  1. BlindBandit

    BlindBandit Orbital Explorer

    Hey guys, new to SMAPI and hoping someone can help out. Here's my code:

    Code:
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Graphics;
    using StardewModdingAPI;
    using StardewModdingAPI.Events;
    using StardewValley;
    using System;
    using System.Collections.Generic;
    
    namespace Julie
    {
        /// <summary>The mod entry point.</summary>
        public class ModEntry : Mod
    {
        /*********
        ** Public methods
        *********/
        /// <summary>Initialise the mod.</summary>
        /// <param name="helper">Provides methods for interacting with the mod directory, such as read/writing a config file or custom JSON files.</param>
        public override void Entry(IModHelper helper)
        {
            GameEvents.OneSecondTick += this.checkJulieExistence;
            PlayerEvents.LoadedGame += this.AddJulieToGame;
        }
    
    
        /*********
        ** Private methods
        *********/
        /// <summary>The method invoked when the player presses a keyboard button.</summary>
        /// <param name="sender">The event sender.</param>
        /// <param name="e">The event data.</param>
        private void checkJulieExistence(object sender, EventArgs e)
        {
            if (Game1.getCharacterFromName("Julie") == null) {
                this.Monitor.Log("XXX");
            } else {
                this.Monitor.Log("OOO Julie exists");
            }
        }
    
        private void AddJulieToGame(object sender, EventArgs e)
        {
            if (Game1.getCharacterFromName("Julie") == null) {
                if (Game1.getLocationFromName("Town") != null) {
                    Game1.getLocationFromName("Town").addCharacter(
                        new NPC(
                            new AnimatedSprite(
                                Game1.content.Load<Texture2D>("Characters\\Julie"),
                                0,
                                Game1.tileSize / 4,
                                Game1.tileSize * 2 / 4
                            ),
                            new Vector2(
                                (float) (18 * Game1.tileSize),
                                (float) (54 * Game1.tileSize)
                            ),
                            "Town",
                            2,
                            "Julie",
                            false,
                            (Dictionary<int, int[]>) null,
                            Game1.content.Load<Texture2D>("Portraits\\Julie")
                        )
                    );
                    this.Monitor.Log("Julie added!");
                    if (Game1.getCharacterFromName("Julie") != null) {
                        NPC.populateRoutesFromLocationToLocationList();
                        Game1.getCharacterFromName("Julie").reloadSprite();
                        this.Monitor.Log("Reloaded Julie sprite!");
                    } else {
                        this.Monitor.Log("Julie added but not found.");
                    }
                } else {
                    this.Monitor.Log("Town location could not be found.");
                }
            } else {
                this.Monitor.Log("Julie already loaded.");
            }
        }
    }
    }
    
    Basically, I'm using SMAPI to add a new NPC to the Town map. A PlayerEvents.LoadedGame event adds the NPC using my AddJulieToGame function. I also have a GameEvents.OneSecondTick event that, every second, uses my checkJulieExists function to check if the Julie character has been added.

    The problem is, AddJulieToGame runs once after you load a new game and the logs indicate that the Julie NPC was added successfully. However, the checkJulieExists logs always show Julie isn't available. And when I play the game and go to the Town map, Julie isn't there.

    However, if I change it so that AddJulieToGame runs instead from a GameEvents.OneSecondTick event, then it works fine, and Julie will be there on the Town map (HalfSecondTick, UpdateTick, etc. all work too). I don't like this solution though, because I'm unnecessarily executing this code at least once a second, when it only ever needs to be run once.

    Looking at http://canimod.com/guides/creating-a-smapi-mod#mod-apis, I've tried GameEvents.Initialize, LoadContent and GameLoaded, and PlayerEvents.LoadedGame for the AddJulieToGame function. All have had the same problem.

    If anyone knows of a better way event in the SMAPI that I could be using (or any other option better than relying on GameEvents.OneSecondTick), I'd appreciate it.
     
    • Entoarox

      Entoarox Oxygen Tank

      Code:
      GameEvents_UpdateTick(object s, EventArgs e)
      {
          if(!Game1.hasLoadedGame)
              return;
          GameEvents.UpdateTick-=GameEvents_UpdateTick;
          AddJulieToGame();
      }
      GameLoaded is far to soon a event, you need to add the NPC after the world is ready, the above event (if registered properly in entry) will do so.
       
      • BlindBandit

        BlindBandit Orbital Explorer

        Is there a better option than relying on any of GameEvents Tick events? (UpdateTick, OneSecondTick, etc.).

        GameEvents.UpdateTick executes ~60 times a second. Using GameEvents.OneSecondTick is better since it would only execute it every second. But I only need it to execute once after the world's loaded, to add the character. Every time it runs after that, it's just an inefficient waste of performance/ and processing.

        I know it's incredibly minimal and I'm being pedantic, but is there truly no better option?
         
        • Entoarox

          Entoarox Oxygen Tank

          That is why it unsubscribes once it does its work, it checks every tick until the world is ready, but after does not have any impact.
          If you want to be less inefficient, you need to depend on EntoFramework and listen to the MoreEvents.WorldReady event, that is essentially a wrapper around the code above, but makes it so the "should fire" only gets checked once.
           
          • BlindBandit

            BlindBandit Orbital Explorer

            Ahhhhh okay, I didn't fully understand the brilliance of GameEvents.UpdateTick-=GameEvents_UpdateTick. I'm really happy with that, thanks Ento :)

            Out of curiosity, how would I depend on the EntoFramework? (I write Java and Perl at work, before this weekend I'd never touched C#, so the way it handles outside packages is completely foreign to me. I'm guessing EntoFramework is your baby? Or the name Entoarox is just a coincidence?)
             
            • Entoarox

              Entoarox Oxygen Tank

              Mostly my baby yes, but there is some work by other people included.
              To depend on the framework, just download it, and add a dependency to the dll, then, just tell people to make sure EntoFramework is installed, and your mod will be able to use any functionality EntoFramework provides. :)
               

              Share This Page