As was brought up in the “guidelines for melee weapons stats” (or something) thread, there are a number of melee weapons that due to their stats are excessively good as thrown weapons as well. I’ll lay out how throwing performance is calculated, and we can discuss what we can do to bring throwing performance in line with what you would actually expect.
Here’s the code that determines accuracy: (I’ve added some extra explanations and removed extraneous code)
int deviation = 0;
// Thrown weapons get an automatic range penalty compared to ranged weapons.
int trange = 1.5 * rl_dist(p.posx, p.posy, tarx, tary);
// This chunk is all about skills and stats, so not really pertinent to the discussion.
int skillLevel = p.skillLevel("throw");
if (skillLevel < 3) deviation += rng(0, 8 - skillLevel);
if (skillLevel < 8) deviation += rng(0, 8 - skillLevel);
else deviation -= skillLevel - 6;
deviation += p.throw_dex_mod();
if (p.per_cur < 6) deviation += rng(0, 8 - p.per_cur);
else if (p.per_cur > 8) deviation -= p.per_cur - 8;
deviation += rng(0, p.encumb(bp_hands) * 2 + p.encumb(bp_eyes) + 1);
// "Oversized" items get a penalty, starting at about baseball bat sized items.
if (thrown.volume() > 5)
deviation += rng(0, 1 + (thrown.volume() - 5) / 4);
// Extremely small items also get a penalty.
if (thrown.volume() == 0)
deviation += rng(0, 3);
// This is um... weird, and should probably be fixed.
// You have an ideal weight based on your strength, and the further away from that weight
// the item is, the bigger your penalty, so if you're super-strong,
// it's very difficult to throw small items.
// I suspect what it WANTED to do was impose a penalty for high weight items
// without giving a bonus for light items. Filed an issue, moving on...
deviation += rng(0, 1 + abs(p.str_cur - thrown.weight() / 113));
That’s actually it, all it cares about is volume and weight. The simplest thing we could do is add a “throwable” flag, and either a bonus for having it, a penalty for not having it, or both. I think that’s sufficient, but if it’s really needed we could have multiple levels of throwability, or possibly a seperate to-hit and/or damage stat for throwing vs melee. There’d have to be a really good reason though.
On to damage, it’s calculated like this:
// Sum up item weight, item bash damage stat, and character strength.
// Then divide by volume of the item.
int real_dam = (thrown.weight() / 452 +
thrown.type->melee_dam / 2 +
p.str_cur / 2) /
double(2 + double(thrown.volume() / 4));
// Cap damage based on item weight, no knocking zombies over with feathers.
if (real_dam > thrown.weight() / 40)
real_dam = thrown.weight() / 40;
// Special boost for railgun.
if (p.has_active_bionic("bio_railgun") && (thrown.made_of("iron") || thrown.made_of("steel")))
{
real_dam *= 2;
}
...
// This determines if you "stick" a item with sharp bits, it's totally based on skill.
if (rng(0, 100) < 20 + skillLevel * 12 && thrown.type->melee_cut > 0) {
// Only factor in cutting armor if we're doing cutting damage.
// The code does account for bash armor,
// but it's kind of scattered around and not interesting for our purposes.
if (thrown.type->melee_cut > z.armor_cut())
dam += (thrown.type->melee_cut - z.armor_cut());
}
This overall seems sensible, but there are a few things that jump out at me that could be improved.
One is that items are more likely to miss at long range, but they do exactly the same amount of damage, no matter how for away the target is. With small dense items this is fine, but unaerodynamic items should have significant slowdown from air resistance. Something like losing damage with each space travelled based on volume/weight, possibly with a “thrown” flag factored in. Good throwing weapons like rocks wouldn’t lose anything, but high volume to low weight items like blankets or paper wrappers would lose huge amounts.
It assumes all items with cutting damage are like throwing knives, if something is thrown like a spear, it’s not an issue, so we need a flag to either trigger this or bypass it.
It seems like cutting damage should scale with how hard you throw it.
A minor point is that if you accidentally hit a monster with e.g. a throwing knife, it still applies your skill to determine if it “sticks”, it should be totally random in this case. (if you don’t know the range, you can’t throw it correctly)
As for differentiating between items intended for throwing and not, there is the same choices, either a “good for throwing” tag, or seperate throwing stats. Or something else better of course.
Finally we have range determination:
// If it's too heavy relative to strength, we can't throw it at all.
if ((tmp.weight() / 113) > int(str_cur * 15))
return 0;
// Increases as weight decreases until 150 g, then decreases again
int ret = (str_cur * 8) / (tmp.weight() >= 150 ? tmp.weight() / 113 : 10 - int(tmp.weight() / 15));
// Decrease with volume, anything under 4 gets no penalty.
ret -= int(tmp.volume() / 4);
// Special boost for railgun.
if (has_active_bionic("bio_railgun") && (tmp.made_of("iron") || tmp.made_of("steel")))
ret *= 2;
if (ret < 1)
return 1;
// Cap at double our strength + skill
if (ret > str_cur * 1.5 + skillLevel("throw"))
return str_cur * 1.5 + skillLevel("throw");
Everything here seems fine, but of course there’s room to factor in throwability.
After looking through these, I tenatively think a “throwable” flag added to select weapons, with appropriate bonuses applied across the board would do what we need, but if anyone has ideas on something better, let’s hear them.
Also I’ve turned up a few bugs and opportunities for improvement, all of which would be internal to the throwing code except for adding a flag for “tumbling cutting weapon” vs “non-tumbling cutting weapon”, given stuff like thrown swords, I think the best option is a special flag for “non-tumbling thrown weapons”, to be applied to javelins, spears, darts, shuriken and any other weapons that don’t require extra effort to make them stick when thrown.
Also I think I’ll stick most of the comments I added for readability here into the actual code