1. Welcome to the official Starbound Mod repository, Guest! Not sure how to install your mods? Check out the installation guide or check out the modding help thread for more guides.
    Outdated Mods have been moved to their own category! If you update your mod please let a moderator know so we can move it back to the active section.
    Dismiss Notice

Interaction Helper 1.0.3

Displays a popup over NPCs and animals that still need their daily interaction.

  1. v1.0.3

    Hammurabi
    Updated for Stardew Valley v1.2 / SMAPI v1.10 compatibility.

    The update was easier than expected, so I managed to sneak it in before work.

    Source code:
    Code:
    using System;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Content;
    using Microsoft.Xna.Framework.Graphics;
    using StardewModdingAPI;
    using StardewModdingAPI.Events;
    using StardewValley;
    using StardewValley.Characters;
    
    namespace InteractionHelper {
       public class InteractionHelper : Mod
      {
         private Texture2D symbols = null;
         private Config config;
         private IModHelper modHelper;
         private int tickType = 1;
         private bool betterRanchingDetected;
    
         private const int heart = 0;
         private const int talk = 1;
         private const int birthday = 2;
         private const int gift = 3;
         private const int milkPail = 4;
         private const int shears = 5;
    
         private const int spriteSize = 32;
    
         public override void Entry(IModHelper helper) {
           modHelper = helper;
           TimeEvents.DayOfMonthChanged += AfterLoad;
           GameEvents.HalfSecondTick += ToggleDisplayType;
           GraphicsEvents.OnPreRenderHudEvent += DisplayIcons;
    
           config = helper.ReadConfig<Config>();
         }
    
         public void AfterLoad(object sender, EventArgs e) {
           try {
             symbols = new ContentManager(Game1.content.ServiceProvider, modHelper.DirectoryPath + "\\").Load<Texture2D>("Symbols");
           }
           catch (Exception ex) {
             Monitor.Log($"Failed to load symbol texture: {ex.Message}", LogLevel.Error);
           }
    
           if (modHelper.ModRegistry.IsLoaded("BetterRanching")) {
             betterRanchingDetected = true;
             Monitor.Log("Better Ranching detected. Farm animal displays will be partially suppressed for compatibility.", LogLevel.Info);
           }
    
           TimeEvents.DayOfMonthChanged -= AfterLoad;
         }
    
         public void ToggleDisplayType(object sender, EventArgs e) {
           tickType = (tickType + 1) % 2;
         }
    
         public void DisplayIcons(object sender, EventArgs e) {
           if (symbols != null) {
             foreach (Character c in Game1.currentLocation.characters) {
               ProcessCharacter(c);
             }
    
             if (Game1.currentLocation is Farm) {
               foreach (FarmAnimal f in ((Farm) Game1.currentLocation).animals.Values) {
                 ProcessFarmAnimal(f);
               }
             }
             else if (Game1.currentLocation is AnimalHouse) {
               foreach (FarmAnimal f in ((AnimalHouse) Game1.currentLocation).animals.Values) {
                 ProcessFarmAnimal(f);
               }
             }
           }
         }
    
         public void ProcessCharacter(Character c) {
           if (c is Horse) { return; }
           if (c is Junimo) { return; }
           if (c is StardewValley.Monsters.Monster) { return; }
           if ("Gunther".Equals(c.name)) { return; }
           if ("Marlon".Equals(c.name)) { return; }
           if ("Dwarf".Equals(c.name) && !config.showDwarf) { return; }
           if ("Krobus".Equals(c.name) && !config.showKrobus) { return; }
    
           if (c is Pet) { ProcessPet((Pet) c); }
           else if (c is NPC && Game1.NPCGiftTastes.ContainsKey(c.name)) { ProcessNPC((NPC) c); }
           else if (c is Child) { ProcessChild((Child) c); }
         }
    
         public void ProcessFarmAnimal(FarmAnimal farmAnimal) {
           if (!betterRanchingDetected || farmAnimal.currentProduce == -1 || farmAnimal.harvestType == 0 || string.IsNullOrWhiteSpace(farmAnimal.toolUsedForHarvest)) {
             bool wideSprite = !farmAnimal.isCoopDweller();
             float verticalOffset = farmAnimal.isCoopDweller() ? -(Game1.tileSize * (4f / 3f)) : -(Game1.tileSize * (2f / 3f));
    
             if (config.showFarmAnimalProduceIndicator && farmAnimal.currentProduce >= 0 && farmAnimal.harvestType == 1 && "Shears".Equals(farmAnimal.toolUsedForHarvest)) { ShowDisplay(farmAnimal, shears, wideSprite, verticalOffset); }
             else if (config.showFarmAnimalProduceIndicator && farmAnimal.currentProduce >= 0 && farmAnimal.harvestType == 1 && "Milk Pail".Equals(farmAnimal.toolUsedForHarvest)) { ShowDisplay(farmAnimal, milkPail, wideSprite, verticalOffset); }
             else if (config.showFarmAnimalHeartIndicator && !farmAnimal.wasPet) { ShowDisplay(farmAnimal, heart, wideSprite, verticalOffset); }
           }
         }
    
         public void ProcessPet(Pet pet) {
           if (config.showCatOrDogHeartIndicator && !modHelper.Reflection.GetPrivateField<bool>(pet, "wasPetToday", true).GetValue()) {
             ShowDisplay(pet, heart, true, -(Game1.tileSize * (2f/3f)));
           }
         }
    
         public void ProcessNPC(NPC npc) {
           if (Game1.player.friendships.ContainsKey(npc.name)) {
             bool needsBirthdayGift = config.showBirthdayIndicator && npc.isBirthday(Game1.currentSeason, Game1.dayOfMonth) && Game1.player.friendships[npc.name][3] != 1;
             bool needsRegularGift = config.showGiftIndicator && ((Game1.dayOfMonth % 7) - 4) > Game1.player.friendships[npc.name][1];
             bool needsDailyTalk = config.showTalkIndicator && !Game1.player.hasPlayerTalkedToNPC(npc.name);
    
             if ((needsBirthdayGift || needsRegularGift) && needsDailyTalk) {
               if (tickType == 0) { ShowDisplay(npc, needsBirthdayGift ? birthday : gift, false, -(Game1.tileSize * 1.5f)); }
               else { ShowDisplay(npc, talk, false, -(Game1.tileSize * 1.5f)); }
             }
             else if (needsBirthdayGift) { ShowDisplay(npc, birthday, false, -(Game1.tileSize * 1.5f)); }
             else if (needsRegularGift) { ShowDisplay(npc, gift, false, -(Game1.tileSize * 1.5f)); }
             else if (needsDailyTalk) { ShowDisplay(npc, talk, false, -(Game1.tileSize * 1.5f)); }
           }
           else {
             ShowDisplay(npc, talk, false, -(Game1.tileSize * 1.5f));
           }
         }
    
         public void ProcessChild(Child child) {
           if (config.showTalkIndicator && child.age >= 1 && (!Game1.player.friendships.ContainsKey(child.name) || !Game1.player.hasPlayerTalkedToNPC(child.name))) {
             ShowDisplay(child, talk, false, -(Game1.tileSize * 1.5f));
           }
         }
    
         private void ShowDisplay(Character c, int symbolID, bool wideSprite, float verticalOffset) {
           float horizontalOffset = (wideSprite && (c.facingDirection == 0 || c.facingDirection == 2)) ? c.Sprite.getWidth() : c.Sprite.getWidth() / 2f;
    
           float bobOffset = (float) (4.0 * Math.Round(Math.Sin(DateTime.Now.TimeOfDay.TotalMilliseconds / 250.0), 2));
           Game1.spriteBatch.Draw(Game1.mouseCursors, Game1.GlobalToLocal(Game1.viewport, new Vector2(c.Position.X + horizontalOffset, c.Position.Y - c.Sprite.getHeight() + verticalOffset + bobOffset)), new Rectangle?(new Rectangle(141, 465, 20, 24)), Color.White * 0.75f, 0.0f, Vector2.Zero, 4f, SpriteEffects.None, 0.0f);
           Game1.spriteBatch.Draw(symbols, Game1.GlobalToLocal(Game1.viewport, new Vector2(c.Position.X + horizontalOffset + (Game1.tileSize * 9 / 20), c.Position.Y - c.Sprite.getHeight() + verticalOffset + (Game1.tileSize * 0.5f) + bobOffset)), new Rectangle?(GetSpriteRectangle(symbolID)), Color.White * 0.75f, 0.0f, new Vector2(8f, 8f), Game1.pixelZoom * 0.35f, SpriteEffects.None, 1f);
         }
    
         private Rectangle GetSpriteRectangle(int symbolID) {
           int x = symbolID % 2;
           int y = (symbolID - x) / 2;
           return new Rectangle(x * spriteSize, y * spriteSize, spriteSize, spriteSize);
         }
       }
    
       public class Config {
         public bool showDwarf { get; set; } = true;
         public bool showKrobus { get; set; } = true;
    
         public bool showBirthdayIndicator { get; set; } = true;
         public bool showGiftIndicator { get; set; } = true;
         public bool showTalkIndicator { get; set; } = true;
         public bool showCatOrDogHeartIndicator { get; set; } = true;
         public bool showFarmAnimalHeartIndicator { get; set; } = true;
         public bool showFarmAnimalProduceIndicator { get; set; } = true;
       }
    }
    
Return to update list...