Uploaded image for project: 'Minecraft: Java Edition'
  1. Minecraft: Java Edition
  2. MC-157136

Villagers restocking is not properly tracked

XMLWordPrintable

    • Confirmed
    • Trading

      The bug

      Villagers do not properly keep track of the last time they restocked, neither the number of times they have restocked in the day. This causes villagers indefinitely restocking each time they visit their POI during their work hours. This happens with each villager once the world has passed a a half-day of gametime.

      How to reproduce

      1. Let the world gametime reach at least 12,000 ticks (/time query gametime to check).
      2. Spawn a villager in an empty superflat world with access to a POI
      3. Trade with this villager during work hours

      The villager will restock every time he works at his workstation, leading to the possibility of abusing his trades (MissLauralot's 2nd screenshot shows 4 stacks of emeralds obtainable in one day), even while the GUI is still open (Johnibur's video: 2019-07-23 12-11-49.mp4).

      As a result of the restocks not being tracked, the demand being reduced at every restock will absurdly go down, in the wrong direction (MissLauralot's 1st screenshot, making the prices not affected by an excessive use of the same trades.

      The nbt values LastRestock and RestocksToday always stay at 0.

      Code analysis by Johnibur:

      This method (own mappings) from the Entity Villager class performs the restocking (set trades use values to 0) and set tracking values which are used for the hasNotAlreadyRestocked() condition further below:

          public void restock() {
              this.goSetDemand();
              
              Iterator iterator = this.getOffers().iterator();
             
              // Do the restock
              while (iterator.hasNext()) {
                  MerchantRecipe merchantrecipe = (MerchantRecipe) iterator.next();
                  
                  merchantrecipe.resetUses();
              }
              
              // (…)
              if (this.getVillagerData().getProfession() == VillagerProfession.FARMER) {
                  this.eE();
              }
      
              // Set the tracking values
              this.lastRestock = this.world.getTime();
              ++this.restocksToday;
          }
      

       

      It is called each time the villager works at his workstation, provided the following requirement is met:

       public boolean isRestockNeeded() {
      
              // This is for wether or not calling for demand
              long i = this.lastRestock + 12000L;
              boolean flag = this.world.getTime() > i;
              long j = this.world.getDayTime();
      
              if (this.bP > 0L) {
                  long k = this.bP / 24000L;
                  long l = j / 24000L;
      
                  flag |= l > k;
              }
              this.bP = j;
              if (flag) {
                  // method preparing to set the demand
              }
      
              // Real conditions
              return this.hasNotAlreadyRestocked() && this.IsOutOfStock();
          }
      

       
      The method isOutOfStock() checks whether any stock has been depleted:

      private boolean isOutOfSTock() {
              Iterator iterator = this.getOffers().iterator();
                                                
              MerchantRecipe merchantrecipe;
      
              do {
                  if (!iterator.hasNext()) {
                      return false;  // Can't find any fully used offer
                  }
      
                  merchantrecipe = (MerchantRecipe) iterator.next();
              } while (!merchantrecipe.isFullyUsed());
      
              return true;
          }
      

       
      Yet, the restock can already be potentially done inside the method called for demand:

       private void prepareforSettingDemand() {
              int i = 2 - this.restocksToday;
      
             // This creates the issue: restock is prematurely been made here.
              if (i > 0) {
                  Iterator iterator = this.getOffers().iterator();
      
                  while (iterator.hasNext()) {
                      MerchantRecipe merchantrecipe = (MerchantRecipe) iterator.next();
      
                      merchantrecipe.resetUses();
                  }
              }
              for (int j = 0; j < i; ++j) {
                  this.setDemand();
              }
          }
      

       

      This leads to isRestockNeeded() always be false once a certain amount of gametime has passed, because the restock is already done in the call for demand methods, but not tracked at all. Thus properly restocking is not possible.

        1. 2019-07-20_00.32.54.png
          623 kB
          Laura
        2. 2019-07-20_00.33.19.png
          329 kB
          Laura
        3. 2019-07-23 12-11-49.mp4
          7.46 MB
          [Helper] Johnibur
        4. 2019-08-29_15.50.38 (2019_08_29 19_59_56 UTC).png
          228 kB
          Austin Brancheau
        5. 2019-08-29_15.50.39 (2019_08_29 19_59_56 UTC).png
          231 kB
          Austin Brancheau

            cojomax99 [Mojang] Cory Scheviak
            MissLauralot Laura
            Votes:
            13 Vote for this issue
            Watchers:
            15 Start watching this issue

              Created:
              Updated:
              Resolved:
              CHK: