1. This forum is archived for reference. For support & bug reports visit the help section of forums.stardewvalley.net

Bug/Issue 1.3 - Animal Mood Bugs

Discussion in 'Support' started by Jurk0wski, May 23, 2018.

  1. Jurk0wski

    Jurk0wski Orbital Explorer

    First, some background on animal mechanics. Animals have 2 stats: Friendship and Mood. Friendship is just like villager friendship; it raises over time and is tracked by hearts. Friendship has a max value of 1000. Mood is highly variable, capable of plummeting if not fed one day or maxing out if the animal eats grass outdoors, and it is tracked by the text beneath the animal's friendship hearts. Mood has a maximum value of 255 (this is also known as byte.MaxValue, which is the maximum value you can store in a byte). When considering what type and what quality of animal products you recieve, the game uses the friendship and mood values of the animal at the time you went to bed the night before. Friendship impacts type/quality the most, while Mood impacts it less. Key point though: even at maximum friendship, it is statistically impossible to get iridium quality animal products below 214 Mood, or if you have either the barn or coop lvl 10 farming professions and it's the appropriate animal for that profession you need at least 140 Mood.

    With that out of the way, here are two bugs that have impacted animal moods for a long time, and still do in the 1.3 beta:

    #1: In most cases, animals with moods above 150 will drain their mood a bit every 10 in-game minutes starting from 6PM until the player(s) go to bed.

    Bug: In all seasons except winter, and in winter without a heater, all animals, indoors, with a mood above 150 will have their mood drained by a set amount every 10 in-game minutes starting from 6PM until their mood drops to 150 or lower, or the player goes to bed. The only exception is when it is Winter and a heater exists, in which case animals, indoors, with a mood above 150 will have their mood raised every 10 minutes starting at 6PM by the same rate it would have drained. (Note: this only impacts animals whose mood is already above 150)

    How to reproduce: we'll do two tests. These tests will be done with the assumption that people cannot see the exact mood value of an animal, so dev tools will make this easier. First, have a save game backed up with an animal, in a building with its door closed, in any non-winter season with outdoor grass available and it not raining. For the first test, let it out to eat some grass, wait until 6PM, and then go to bed. Do not wait for the animal to eat grass and then immediately go to bed early. It seems sometimes animals will think they didn't eat if you go to bed early, likely due to thinking they were stuck outdoors overnight, killing their mood anyways. The following day, immediately check the animal's mood before letting it do anything else. You'll find that they "looks really happy today" which is the same message they would give you immediately after eating grass outdoors (some exceptions apply, but it's usually due to not eating the day before). Now, for the second test, do exactly the same, but stay up until 10PM this time. When you next go and immediately check the animal's mood, they will only "looks fine" (once again, some exceptions apply, usually though it's because you forgot to close the door and they already ate grass before you reached them).

    Why this happens: Thanks to decompiler tools linked to on the modder wiki, we can look at the game's code. Whether or not this is exactly the same as what the devs see, I dunno, but it still lets me point out the problems. Here's the problematic section snipped down to the relevant parts:

    Code:
        public virtual void updatePerTenMinutes(int timeOfDay, GameLocation environment)
        {
          if (timeOfDay >= 1800)
          {
            if (environment.IsOutdoors && timeOfDay > 1900 || !environment.IsOutdoors && (byte) ((NetFieldBase<byte, NetByte>) this.happiness) > (byte) 150 || ((bool) ((NetFieldBase<bool, NetBool>) environment.isOutdoors) && Game1.isRaining || (bool) ((NetFieldBase<bool, NetBool>) environment.isOutdoors) && Game1.currentSeason.Equals("winter")))
              this.happiness.Value = (byte) Math.Min((int) byte.MaxValue, Math.Max(0, (int) (byte) ((NetFieldBase<byte, NetByte>) this.happiness) - (environment.numberOfObjectsWithName("Heater") <= 0 || !Game1.currentSeason.Equals("winter") ? (int) (byte) ((NetFieldBase<byte, NetByte>) this.happinessDrain) : (int) -(byte) ((NetFieldBase<byte, NetByte>) this.happinessDrain))));
            else if (environment.IsOutdoors)
              this.happiness.Value = (byte) Math.Min((int) byte.MaxValue, (int) (byte) ((NetFieldBase<byte, NetByte>) this.happiness) + (int) (byte) ((NetFieldBase<byte, NetByte>) this.happinessDrain));
          }
        ....
        }
    
    To sum it up: If the game time is 6PM or later, do this part of the function. First, if the game time is 7PM or later and the animal is outdoors, or the animal is indoors and has a happiness greater than 150, or it is raining and the animal is outdoors, or it is winter and the animal is outdoors, then the animal's mood is going to shift every 10 minutes. If it is winter and a heater exists, the animal's mood will be decreased by a negative value, thus increasing it. If it is not winter or if a heater doesn't exist in winter, the animal's mood will be decreased by a positive amount, thus decreasing. The problem here is that regardless of the situation, an animal whose mood is above 150 will always have its mood shifted every 10 minutes starting at 6PM, almost always negatively, except in winter with a heater.

    How to fix it: The mood check seems to exist specifically with winter in mind, as an animal without a heater will be unhappy, but not entirely miserable while indoors, so its mood should not so much drain as it should just equalize in winter without a heater. The keyword here every time is Winter. An easy fix would be to change this:

    Code:
    !environment.IsOutdoors && (byte) ((NetFieldBase<byte, NetByte>) this.happiness) > (byte) 150
    to this:
    Code:
    !environment.IsOutdoors && (byte) ((NetFieldBase<byte, NetByte>) this.happiness) > (byte) 150 && Game1.currentSeason.Equals("winter")
    This will make the mood check only apply during winter, meaning non-winter seasons will no longer have the drain except when you're actually abusing your animals, like leaving them outside in the rain or late at night.

    #2: Petting an animal at or near max mood while you have the appropriate lvl 10 farming profession for that animal will overflow the value to nearly 0.

    Bug: When you pet for the first time in a day a barn animal while you have the Shepherd lvl 10 farming profession, or a coop animal while you have the Coopmaster lvl 10 farming profession while the animal in question is at or near maximum mood, its mood will overflow past 255, and then be set to a value at or near 0. This bug used to exist for all petting, but a while back regular petting was fixed.

    How to reproduce: Let an animal eat grass outdoors, and then pet them without the profession, they should "looks really happy today" (with some exceptions as above). Now do the same but with that animal's appropriate lvl 10 profession, and they'll "looks sad" (one exception, if you pet them and then they eat grass outside, their mood will max out again after your petting).

    Why this happens: let me pull up the petting specific code here, once again it's just a snippet:

    Code:
    public void pet(Farmer who)
        {
        ....
            if (!(bool) ((NetFieldBase<bool, NetBool>) this.wasPet))
            {
              this.wasPet.Value = true;
              this.friendshipTowardFarmer.Value = Math.Min(1000, (int) ((NetFieldBase<int, NetInt>) this.friendshipTowardFarmer) + 15);
              if (who.professions.Contains(3) && !this.isCoopDweller())
              {
                this.friendshipTowardFarmer.Value = Math.Min(1000, (int) ((NetFieldBase<int, NetInt>) this.friendshipTowardFarmer) + 15);
                this.happiness.Value = Math.Min(byte.MaxValue, (byte) ((uint) (byte) ((NetFieldBase<byte, NetByte>) this.happiness) + (uint) Math.Max(5, 40 - (int) (byte) ((NetFieldBase<byte, NetByte>) this.happinessDrain))));
              }
              else if (who.professions.Contains(2) && this.isCoopDweller())
              {
                this.friendshipTowardFarmer.Value = Math.Min(1000, (int) ((NetFieldBase<int, NetInt>) this.friendshipTowardFarmer) + 15);
                this.happiness.Value = Math.Min(byte.MaxValue, (byte) ((uint) (byte) ((NetFieldBase<byte, NetByte>) this.happiness) + (uint) Math.Max(5, 40 - (int) (byte) ((NetFieldBase<byte, NetByte>) this.happinessDrain))));
              }
              this.doEmote((int) ((NetFieldBase<int, NetInt>) this.moodMessage) == 4 ? 12 : 20, true);
              this.happiness.Value = (byte) Math.Min((int) byte.MaxValue, (int) (byte) ((NetFieldBase<byte, NetByte>) this.happiness) + Math.Max(5, 40 - (int) (byte) ((NetFieldBase<byte, NetByte>) this.happinessDrain)));
              if (this.sound.Value != null && Game1.soundBank != null)
            ....
            }
            ....
          }
        }
    
    Now, let me pull out the code for regular petting:

    Code:
    this.happiness.Value = (byte) Math.Min((int) byte.MaxValue, (int) (byte) ((NetFieldBase<byte, NetByte>) this.happiness) + Math.Max(5, 40 - (int) (byte) ((NetFieldBase<byte, NetByte>) this.happinessDrain)));
    
    let's run through what happens: Some number, dependent on animal type, is type-cast to an integer (perfectly fine) and subtracted from 40, and then compared to 5, the higher number is kept. This number is still an integer. The animal's current mood is then type-cast to an integer, and has the previous number added to it to make a new number that hasn't been applied to any variable yet. You then get the maximum value a byte can hold and type-cast it to an integer as well, and then compare that number (255) to the previous number you got, and you keep the smaller of the two. Finally, you type-cast that integer back to a byte, and make it the animal's new mood. So in case the added number is larger than 255, 255 is chosen instead so the mood value doesn't overflow.

    Now, let me pull out the code for profession petting (the same line is used for both professions, just in two seperate places):

    Code:
    this.happiness.Value = Math.Min(byte.MaxValue, (byte) ((uint) (byte) ((NetFieldBase<byte, NetByte>) this.happiness) + (uint) Math.Max(5, 40 - (int) (byte) ((NetFieldBase<byte, NetByte>) this.happinessDrain))));
    
    let's run through what happens with this one: Some number, dependent on animal type, is type-cast to an integer, subtracted from 40, compared to 5, and you keep the larger number (still all fine). This value is also type-cast to an unsigned integer (still fine, just odd). The animal's mood is then type-cast to an unsigned integer and has the previous value added to it (still fine) and then this new value is type-cast to a byte (Not Good). The new value is then compared to a byte's maximum value (255) and the smaller of the two is kept. This final value is still a byte, so does not need to be type-cast again and is set as the animal's new mood.

    So, why does it break? Well, in binary, a byte with 0 stored is read as 0000 0000, while a byte with max value is read as 1111 1111. An unsigned integer dependent on the system used can have anywhere from 2-4 bytes used for it. Regardless though, it can at least store 256, which is stored roughly as 0001 0000 0000. If this is type-cast to a byte, it cuts off the unecessary data: 0001 0000 0000, resulting in a byte with the value of 0. In regards to this code, it means that any value above 255 has 256 subtracted from it when it is type-cast into a byte. Since the profession's petting code has this unsigned int cast to a byte before being compared with the lower value kept, if it overflows after the type-cast, the overflow value will be chosen instead of 255. Let's say the animal just fed on grass outside, and the petting should add 30 mood, the animal's mood (255) ends up being set to 29 (285 is 0001 0001 1101 in binary which is ends up becoming 29 which is 0001 0001 1101 in binary), and then an additional 30 would be added (since profession petting triggers the petting code twice for double mood value), resulting in a final mood of 59.

    How to fix it: Fortunately, since the actual intention behind the profession petting lines of code are the same as the non-profession line, you can simply replace the two profession lines with the non-profession line:

    Code:
    public void pet(Farmer who)
        {
        ....
            if (!(bool) ((NetFieldBase<bool, NetBool>) this.wasPet))
            {
              this.wasPet.Value = true;
              this.friendshipTowardFarmer.Value = Math.Min(1000, (int) ((NetFieldBase<int, NetInt>) this.friendshipTowardFarmer) + 15);
              if (who.professions.Contains(3) && !this.isCoopDweller())
              {
                this.friendshipTowardFarmer.Value = Math.Min(1000, (int) ((NetFieldBase<int, NetInt>) this.friendshipTowardFarmer) + 15);
                this.happiness.Value = (byte) Math.Min((int) byte.MaxValue, (int) (byte) ((NetFieldBase<byte, NetByte>) this.happiness) + Math.Max(5, 40 - (int) (byte) ((NetFieldBase<byte, NetByte>) this.happinessDrain)));
              }
              else if (who.professions.Contains(2) && this.isCoopDweller())
              {
                this.friendshipTowardFarmer.Value = Math.Min(1000, (int) ((NetFieldBase<int, NetInt>) this.friendshipTowardFarmer) + 15);
                this.happiness.Value = (byte) Math.Min((int) byte.MaxValue, (int) (byte) ((NetFieldBase<byte, NetByte>) this.happiness) + Math.Max(5, 40 - (int) (byte) ((NetFieldBase<byte, NetByte>) this.happinessDrain)));
              }
              this.doEmote((int) ((NetFieldBase<int, NetInt>) this.moodMessage) == 4 ? 12 : 20, true);
              this.happiness.Value = (byte) Math.Min((int) byte.MaxValue, (int) (byte) ((NetFieldBase<byte, NetByte>) this.happiness) + Math.Max(5, 40 - (int) (byte) ((NetFieldBase<byte, NetByte>) this.happinessDrain)));
              if (this.sound.Value != null && Game1.soundBank != null)
            ....
            }
            ....
          }
        }
    
    Here's hoping one or both of those bugs get fixed soon. They've existed since the game's launch, and, especially the first bug, they make animals far less efficient as income sources.

    Edit: grammar fixes
     
      Last edited: May 23, 2018
      parabolic paradise and Cett like this.
    • Jurk0wski

      Jurk0wski Orbital Explorer

      Still not fixed in the 1.3.13 Update.
       
      • Starryem

        Starryem Space Hobo

        My animals are definitely happier in Winter!
         
        • Blorp

          Blorp Big Damn Hero

          I'm not entirely sure the fix is what was intended in the first place with this code. My guess would be that they tried to consolidate several things at one point and broke it in the process. The way it checks for a heater and winter and then adds rather than subtracts makes me think perhaps the idea was that "being indoors at night should increase happiness, but in winter only if there is a heater", as well as the obvious bad things that should always decrease it. But anyway, they definitely need to revisit this.
           
            Starryem likes this.
          • Kolaris

            Kolaris Void-Bound Voyager

            This is very frustrating for my Rancher friends who wanted to take care of the animals. They're the only ones who aren't allowed to pet them!
             

            Share This Page