Sunday, August 15, 2021

How Fishing Success Is Calculated (Winter Sucks)

On reddit and twitch I've seen some confusion about fishing (especially in winter), so I thought I'd investigate.

The wiki states the following:

  • Fishing at dawn (04:00-06:00) and dusk (18:00-20:00) increases the chance of catching fish
  • Fishing between November and February decreases the chance of catching fish
  • Fishing with a spear is harder
  • Fishing with live bait is easier
  • Fishing with fishing tackle requires higher levels of Fishing skill

The wiki is actually accurate about this, but I wanted to give additional details that are available in the lua code.  ISFishingAction.lua has all the relevant information.

First off, you can set the Sandbox Settings to adjust how easy it is to "attract" fish to catch:

    o.attractNumber = 100;
    if SandboxVars.NatureAbundance == 1 then -- very poor
        o.attractNumber = 140;
    elseif SandboxVars.NatureAbundance == 2 then -- poor
        o.attractNumber = 120;
    elseif SandboxVars.NatureAbundance == 4 then -- abundant
        o.attractNumber = 80;
    elseif SandboxVars.NatureAbundance == 5 then -- very abundant
        o.attractNumber = 60;
    end

For the purposes of this post, let's just assume you are using default settings, so your attract number is 100 and we more easily talk in percentages.

The attractFish() method gives all the information we need:

-- Depend on what lure you used :
-- Living lure is easier but can escape and always removed after getting something
-- Plastic lure are for good fisherman, almost never disapear but harder to get something
function ISFishingAction:attractFish()
    local attractNumber = ZombRand(self.attractNumber);
    -- a bit more chance during dawn and dusk
    local currentHour = math.floor(math.floor(GameTime:getInstance():getTimeOfDay() * 3600) / 3600);
    if (currentHour >= 4 and currentHour <= 6) or (currentHour >= 18 and currentHour <= 20) then
        attractNumber = attractNumber - 10;
    end
    if self.usingSpear then -- less chance to catch something with a spear
        attractNumber = attractNumber + 10;
    end
    -- harder chance of getting fish during winter
    if (getGameTime():getMonth() + 1) >= 11 or (getGameTime():getMonth() + 1) <= 2 then
        attractNumber = attractNumber + 20;
    end
    -- start with plastic lure
    if self.plasticLure and attractNumber <= (10 + (self.fishingLvl * 2.5)) then
        return true;
    elseif not self.plasticLure and attractNumber <= (20 + (self.fishingLvl * 1.5)) then
        return true;
    end
--    return true;
    return false;
end

So, with the default self.attractNumber of 100, we generate a random number (1-100) and call it attractNumber.  If it is low enough, we have attracted fish, and that number is modified in the following ways:

  • Fishing at dawn and dusk increases the chance of catching fish by 10%
  • Fishing between November and February decreases the chance of catching fish by 20%
  • Fishing with a spear is harder by 10%
  • Fishing with live bait is easier than fishing tackle by 10% at Level 0
And when it is better to use fishing tackle rather than live bait?
10 + (self.fishingLvl * 2.5) vs 20 + (selffishingLvl * 1.5)

Never.  At Level 10, fishing tackle will have an equal chance at catching fish as when using live bait.
10 + (10 * 2.5) = 20 + (10 * 1.5) = 35

Before Level 10, it is always to your advantage to you live bait (of course, you need to acquire the bait).

By looking at this code, you can also see how difficult it is to fish during winter if you haven't spent much time leveling up your fishing skill.

Spearfishing in winter with Level 0 Fishing at noon, you have a -10% chance of catching anything.  At dawn or dusk, you are still at 0%.  So you absolutely need some levels of fishing to have any success in winter spearfishing, and even at Level 10 your chances are pretty slim.  And clearly, don't bother fishing outside of dawn or dusk in winter - it is a waste of your time.

Wait, doesn't Fishing Abundance affect your chance of catching fish?


A little bit, but not really.  All that matters is that your Fishing Abundance is over 0%.  After that, it doesn't affect whether or not you catch any fish.  The number of fish in a zone is set when you first encounter that zone, and as you catch fish the Fishing Abundance % goes down to reflect that.  The Fishing Abundance gets updated about 5.5 hours of game time after the last time you caught a fish.

Let's see how I figured that out in the code.  Fishing Abundance has to be greater than 0% in order to catch any fish at all.

        if fishLeft == 0 then
            self.character:SetVariable("FishingFinished","true");
            -- needed to remove from queue / start next.
            ISBaseTimedAction.perform(self);
           return;
        end
    end


    local caughtFish = false;
    if self:attractFish() then -- caught something !

If there are no fish left, it exits and you don't get the opportunity to see if you caught any fish.

Let's take a look at Fishing Abundance.  You can see here in FishingUI.lua that Fishing Abundance is the label attached to zoneProgress.

    self:drawTextCentre(getText("IGUI_FishingUI_FishAbundance") .. self.zoneProgress .. "%", self.width/2, barY, 1,1,1,1, UIFont.Small);

And zoneProgress is the percentage of current fish () (name) / total fish () (original name)

    if zoneClicked then
        local currentFish = tonumber(zoneClicked:getName());
        local totalFish = tonumber(zoneClicked:getOriginalName());
        if not currentFish or not totalFish or currentFish <= 0 or totalFish <= 0 then
            self.zoneProgress = 0;
        else
            self.zoneProgress = math.floor((currentFish / totalFish) * 100);
        end
    end

Back in ISFishingAction we see where these values are originally set:

        local fishLeft = tonumber(updateZone:getName());
        if getGametimeTimestamp() - updateZone:getLastActionTimestamp() > 20000 then
            fishLeft = math.max(ZombRand(10,25) + self.fishingZoneIncrease, 0);
            updateZone:setName(tostring(fishLeft));
            updateZone:setOriginalName(tostring(fishLeft));
        end
        if fishLeft == 0 then
            self.character:SetVariable("FishingFinished","true");
            -- needed to remove from queue / start next.
            ISBaseTimedAction.perform(self);
           return;
        end

This is done if 20,000 "units" have passed between the current Gametime and the LastActionTime.  I'll get to "units" in a sec...

The LastActionTime is updated in two places - when you catch a fish
    if self:attractFish() then -- caught something !
        local fish = self:getFish();
        if updateZone then
            local fishLeft = tonumber(updateZone:getName());
            updateZone:setName(tostring(fishLeft - 1));
            updateZone:setLastActionTimestamp(getGametimeTimestamp());
            if isClient() then updateZone:sendToServer() end
        end
        caughtFish = true;

and when you first register a fishing zone
    if not updateZone then -- register a new fishing zone
        local nbrOfFish = math.max(ZombRand(10,25) + self.fishingZoneIncrease, 0);
        local x,y,z = self.tile:getSquare():getX(), self.tile:getSquare():getY(), self.tile:getSquare():getZ()
        local updateZone = getWorld():registerZone(tostring(nbrOfFish), "Fishing", x - 20, y - 20, z, 40, 40);
        updateZone:setOriginalName(tostring(nbrOfFish));
        updateZone:setLastActionTimestamp(getGametimeTimestamp());
        if isClient() then updateZone:sendToServer() end
    end

So, what is the deal with what I called "units"?

I took a look at LuaManagerGlobalObject.java to get the definition of getGametimeTimestamp:

    @LuaMethod(name="getGametimeTimestamp", global=true)
    public static long getGametimeTimestamp() {
        return GameTime.instance.getCalender().getTimeInMillis() / 1000L;
    }

So it looks like it should be in game seconds.  20,000 seconds is around 5.5 hours.  

So basically, if you wait 5.5 hours after the last time you caught a fish, the fishing spot should be refreshed!


Thanks for reading!  If you see a mistake I made or have a question you'd like me to tackle, let me know!

8 comments:

  1. I think it might've been me that asked you to look at this. I did my own testing (without looking at code) and the results were exactly as you have here.

    Thanks for doing that anyway.

    ReplyDelete
  2. I made this a while back. Don't think they changed the logic, since: https://tiaxx.de/zomboid/fishing/

    ReplyDelete
  3. Thanks for this writeup!

    One thing I've noticed though, in my game it takes longer than 5.5 ingame hours for the abundance to update. If I finish fishing after dusk is over (at roughly 8 pm) and then try again at dusk (4 am), that should be 8 hours - but the fish abundance has not increased.

    Playing with the public v41 release, on survivor difficulty. Only mod I have is tailoringfix, which I doubt affects fishing.

    ReplyDelete
  4. Hmm, a few meters down the river abundance seems to work normally. Maybe it just doesn't get reset if the player is too close? Or a working generator? The fishing spot that has issues is right next to my base.

    ReplyDelete
  5. This is wrong on at least two points

    (1) ZombRand does not generate 1-100 it generates 0-99 so 0 fishing is enough for a 1% catch rate (remember ZombRand generates an int so round decimals down)

    (2) Testing in-game yields results that completely contradict the code - in winter fishing with a spear requires level 4 to get fish, 3 and below return 0 fish even during dusk and dawn - I'm unsure why but we are obviously missing code

    ReplyDelete
  6. Ok a quick investigation yielded the following results

    (1) This is 100% confirmed, Zomboid uses java.util.Random.nextInt() which includes 0 and excludes the given int, so 0-99 0-79 0-59 etc...

    (2) self.plasticLure is TRUE for spears. Spears are compared against a value of 10 + fishingLvl * 2.5 and this means that in winter spears require level 8 (not 7) to catch fish, and during dusk or dawn they require level 4 (not 1, not 0) to catch fish.

    ReplyDelete
  7. https://docs.google.com/spreadsheets/d/1iqGiQD8NgkxWfV_gySkG94UuLvzeUjetMb4wC3HOs_0/

    True values are here

    ReplyDelete

Do Gloves Protect You From Broken Glass?

Yes, gloves protect you from handling broken glass - any pair of gloves.  But gloves are not needed when removing broken glass from a smashe...