Stardew Valley Weapons DPS
I was recently choosing between the Obsidian Edge and Steel Falchion. They have similar damage ratings, with Obsidian Edge having a higher minimum damage, but different speed and crit rates. To properly compare them you need damage-per-second (DPS), something that Stardew Valley doesn't expose. Let's calculate the DPS of each weapon in the game.
Understanding the damage calculation
To find the DPS we'll need to know how the damage calculation works. This is based on my understanding of the game's code, decompiled by veywrn. This is a 1.5 decompilation, so I won't be accounting for any possible changes that came along in 1.6 here.
There's a few things that go into a DPS calculation. First, we want to know the average damage per hit. This is affected by weapon damage, crit chance, crit damage, and the miss rate. Then we need to know how many hits we can put out every second.
To keep things simple, let's assume we have no attack buffs on the farmer (all modifiers = 0).
We first calculate the effective crit chance. This is only affected by a) what kind of weapon you're using, and b) whether you have the Scout profession. Without Scout, the effective crit chance is equal to the base crit chance for swords and clubs, but modified for daggers:
This is further multiplied by 1.5 if you have the Scout profession. The highest crit chance you can have without modifiers is with the Iridium Needle at 11.76% without Scout (17.64% with); the lowest is the Templar's Blade with a flat zero. Aquamarine enchantments give a flat boost of 4.6%.
GameLocation.damageMonster
contains the main logic for damage.
Draw a random integer between min and max, inclusive. This is the starting value of the damage.
Draw a random float between 0 and 1. If this is less than (crit + luck crit / 40) then this is a crit.
If this is a crit, multiply the base damage by the crit multiplier.
Add (the farmer's attack times 3) to the damage. By our earlier assumption, this is zero.
If this is less than 1, set it to 1.
If the farmer's profession is "Fighter", multiply the damage by 1.1.
If the farmer's profession is "Brute", multiply the damage by 1.5. Note that this stacks with Fighter.
If the farmer's profession is "Desperado", multiply the damage by 2.
Add enchantment effects.
Draw a random double between 0 and 1. If this is less than miss chance - miss chance addedPrecision/10, then it's a miss. The division by 10 is in the weapon caller—presumably increased accuracy is something like 10% per point.
The Fighter profession upgrades into Brute, and they stack. The Scout profession upgrades into Desperado. You can't be both a Fighter/Brute and a Scout/Desperado.
Daggers can hit multiple times with their special. If the special is active, then the main difference is that monsters aren't made invincible on any hits but the last.
Miss chance is actually not a property of the farmer, but of the monster they're attacking. It seems to be zero for all monsters except baby slimes.
With this damage algorithm in hand, we can figure out the damage of any one hit.
Understanding weapon speed
The Stardew Valley Wiki seems to help us a bit with weapon speed:
By default with +0 Weapon Speed, swords take 400 milliseconds per action and clubs take 720 milliseconds per action (equivalent to -8 buff). Daggers are much faster than swords.
This is helpful for swords and clubs, but not for daggers. It turns out that the dagger details are a bit more obfuscated in the game code, which is likely why the Wiki doesn't include those details. To make matters worse, this statement from the Wiki is actually entirely wrong, but more on that later.
I decided to dig through the code (again using veywrn's decompilation). The speed seems to be governed by the MeleeWeapon::setFarmerAnimating
method. swipeSpeed
is used to determine the animation speed, and it's based on the weapon speed and farmer speed:
swipeSpeed = 400 - (int)speed * 40 - who.addedSpeed * 40;
swipeSpeed *= 1f - who.weaponSpeedModifier;
The lower the value of swipeSpeed
, the faster the attack. speed
here is the speed value from weapon data, and it's different to the value it appears as in-game. To get the in-game speed from the weapon data, you halve it for swords and daggers, or add 8 and then halve it for clubs. For example, the Bone Sword has a speed of 8 (corresponding to its in-game +4 speed), while the Wooden Blade has 0. This suggests that a +1 speed weapon has as much extra speed as a farmer attacking with a +2 speed buff (which turns out to be almost true). It's also worth noting that the best possible speed buff is listed as +5 in-game; if you're constantly drinking coffee and eating crab cakes (for example) then you might as well have a +4 speed weapon.
For non-daggers, this value is then divided by 5 for clubs and 8 for swords, then multiplied by 1.3. For daggers, this value is divided by 4. The resulting value is used as the animation interval in the animateOnce
function. Something interesting here is that swords and clubs have their attack passed through the doSwipe
function, while daggers simply animate.
The animation interval is used as the delay at the end of an attack. Each frame of an animation has its own length attached to it. For a sword, there are 6 frames, and the first six carry times of 55, 45, 25, 25, and 25 ms respectively (175 ms total). The final frame has a length of twice the animation interval. So, that suggests a total time of 305 ms for the Wooden Blade and 357 ms for the Iron Edge.
To make things a bit more complicated, my understanding of the code suggests that at least 5 frames of an animation need to pass for non-daggers to allow another attack. Daggers can attack again immediately.
I experimentally measured an average 349 ms per swipe on the Wooden Blade using an autoclicker and a lap stopwatch over 60 swings. This suggests that there's other factors here not obvious from the code. This also demonstrates the Wiki is wrong on base sword speed, as 349 ms is well below the 400 ms stated there.
To make things more complex, monsters also appear to be rendered invincible after you damage them with a dagger. The method setInvincibleCountdown
is triggered on a monster after it's hit. The countdown is 150 ms for daggers and 225 ms for non-daggers, but this doesn't actually seem to trigger for non-daggers.
My take-home message from this was that the weapon attack speed logic is substantially more complex than the damage logic, and perhaps even more complex than intended. There are various factors coming together in a way that suggests the timing code was built up over several iterations and tweaked so it "feels right" in play. Some of the factors affecting speed might even be unintentional.
Measuring weapon speed
If we can't intuit the weapon speed from reading the code, we'll simply have to run it. I wrote a SMAPI mod that records the time an action starts and ends. Then I swung a Wooden Blade, Wood Club, and Iron Dirk—a sword, club, and dagger that don't have any speed buffs or debuffs. I recorded the following times:
Weapon | Ticks | Time |
---|---|---|
Wooden Blade | 21 | 350 ms |
Wood Club | 36 | 600 ms |
Iron Dirk | 12 | 200 ms |
I'm delighted that my stopwatch measurement was only off by 1 ms. (By the way, pickaxes, axes, and hoes seem to take 500 ms; scythes take 350 ms like a sword.)
The next step is to check our understanding of speed buffs matches the actual game. We'll want to check a few different weapon combinations. It turns out to not be consistent across different kinds of weapon:
Weapon | Type | Speed buff | Ticks | Time |
---|---|---|---|---|
Wooden Blade | Sword | 0 | 21 | 350 ms |
Iron Edge | Sword | -2 | 24 | 400 ms |
Steel Smallsword | Sword | +2 | 18 | 300 ms |
Dark Sword | Sword | -5 | 29 | 483.3 ms |
Lead Rod | Club | -4 | 46 | 766.7 ms |
The Slammer | Club | -2 | 41 | 683.3 ms |
Kudgel | Club | -1 | 38 | 633.3 ms |
Wood Club | Club | 0 | 36 | 600 ms |
Femur | Club | +2 | 31 | 516.7 ms |
Iron Dirk | Dagger | 0 | 12 | 200 ms |
Galaxy Dagger | Dagger | +1 | 10 | 166.7 ms |
1 point of speed buff on a sword is 25 ms (not the 40 ms that the Wiki says). 1 point of speed buff on a club is ~42 ms. Something else strange is happening here, though. The Kudgel doesn't fit the pattern and instead is only 33.3 ms slower than the Wood Club. I have no explanation for this.
What about a speed buff on the farmer? As best I can tell from the code, this should be equivalent to half an in-game speed buff. I drank a coffee and measured again:
Weapon | Type | Farmer speed | Ticks | Time |
---|---|---|---|---|
Wooden Blade | Sword | 0 | 21 | 350 ms |
Wooden Blade | Sword | +1 | 20 | 333.3 ms |
Kudgel | Club | 0 | 38 | 633.3 ms |
Kudgel | Club | +1 | 37 | 616.7 ms |
Wood Club | Club | 0 | 36 | 600 ms |
Wood Club | Club | +1 | 35 | 583.3 ms |
A point of speed on the farmer corresponds to 2/3 of a point of speed buff on the sword.
I was also curious as to whether the Kudgel's unusual behaviour continued at other speeds. I buffed a Kudgel with 3 emeralds to get it to +6 speed. Then I measured that:
Weapon | Type | Speed buff | Ticks | Time |
---|---|---|---|---|
Kudgel | Club | -1 | 38 | 633.3 ms |
Kudgel | Club | +6 | 20 | 333.3 ms |
That's about 42 ms per point of speed buff, which is consistent with other clubs.
Calculating damage-per-second
We're now, finally, in a position to calculate the DPS of each weapon. We'll assume that swords have a base speed of 350 ms, with a bonus 25 ms per point of speed. Clubs that aren't the Kudgel start at 600 ms and have a bonus 41.65 ms per point of speed. Daggers start at 200 ms and get 33.3 ms per point of speed. This is only kind of relevant, as only +0 or +1 speed daggers exist without forging.
I've ignored invincibility windows for now. The effect of ignoring this, I think, will be to inflate the DPS of the dagger. I've also ignored specials: daggers and clubs are almost defined by their specials, so this is unfair to both of them. For swords, at least, this analysis should be OK.
Dividing the average damage by the weapon swing time in seconds gives us the DPS of each weapon. The worst dagger is the Carving Knife (11), while the best is the Infinity Dagger (412). The worst sword is the Rusty Sword (10) while the best is the Infinity Blade (374). The worst club is the Femur (17), while the best is the Infinity Gavel (221). These are somewhat unsurprising results. Special mention to the Scythe, which is the all-up worst weapon at 6 DPS.
To answer my original question at long last: The Obsidian Edge has a DPS of 104, while the Steel Falchion is quite a lot higher at 155 DPS. The massive increase in crit power and speed definitely overcome the better minimum damage.