|
I figured it out.
https://github.com/OpenBW/openbw/blob/master/bwgame.h
Sonko pointed me to the bwapi source code and I took a look. There is indeed a separate algorithm for "is_air_splash" and it makes two queues, one to deal full damage and one to deal splash.
Let's say L = outer splash radius, M = medium splash radius, S = inner splash radius.
Full queue It builds a queue of units as such: 1) unit can't have same player as bullet's player (if it does, it must be the missile's target) 2) unit can't be the missile firer 3) unit has to be within L distance
Now, if the original bullet's target is in this queue and is within S distance, the queue is truncated to JUST the target, and full damage is dealt to it. Otherwise, a RANDOM unit from this queue is chosen to take full damage.
Splash queue 1) unit can't have same player as bullet's player (if it does, it must be the missile's target) 2) unit can't be the missile firer 3) unit can't be missile target 4) unit can't be interceptor (!!!) 5) unit has to be within L distance
Then for each unit here, it does either 50%, 25%, or no damage depending on distance.
Let's apply it to our problematic cases.
Single target The original target will always be in the Full queue because valkyrie missile has huge L. It doesn't matter whether the target is within S distance because the queue only has size 1 and so the target takes full damage.
The Splash queue omits the original target, so nothing else needs to be done.
Conclusion: single target takes 100% no matter what
Multiple targets As stated above, the original target will always be in the Full queue. Case 1: Target is within S distance of the missile.
It becomes the ONLY element in the queue and takes full damage. We now go to the splash queue. The non-targets within M take 50% and within S take 25%.
Conclusion: target takes 100%, rest take 50% or 25%
Case 2: Target is outside S distance of the missile.
A random unit in the Full queue is chosen to take 100% damage. We now go to the splash queue. The non-targets within M take 50% and within S take 25%.
Conclusion: random unit takes 100%, non-targets take 50% or 25%.
Herein lies the issue -- the splash queue only excludes the target, NOT THE RANDOMLY PICKED UNIT FROM THE FULL QUEUE! So it could be the case that the randomly picked unit was NOT the target, in which case that poor unit would take an additional 50% or 25% damage based on proximity! Let's call this "super damage", when unit takes more than 100% damage.
Note that this super damage can only occur if the target is NOT within S of the missile, because if it were, it would take the 100% from Full queue and be excluded from the Splash queue. Since the missile pattern centers around the target, I imagine that most of the time, the target IS within S of the missile, so we don't see super damage.
Furthermore, it would be the case that the ORIGINAL target actually takes ZERO damage if the random unit picked during Full queue was not the target, since the splash queue specifically omits the target. So the existence of super damage implies the target received zero damage!
Note that the total damage is the same across the cases.
Finally, I think the "diminishing returns" observation is simply due to the fact the average missile misses its target. Let's pretend each missile is a student and each unit is a test question. Now, each student is dumb and scores 50% or 25% normally on any given question. However, the Full queue is a "get out of jail free card" that guarantees you a 100% on exactly one question. So if there is only one question, everyone scores a perfect score. If there are only a few questions, the class average is still high. Add a lot of questions, and it becomes clear that each student is failing.
Another interesting observation is that interceptors are immune to splash damage. In other words, it can never take damage via the Splash queue, so it must get damaged during the Full queue. Every valkyrie missile will hit at most one interceptor for full damage and deal no splash no matter how many interceptors there are. So in fact, valkyries are bad against interceptors. Similarly with corsairs.
Bonus observation:
If a corsair hits a group of say 11 stacked mutas, it will do a full 2.5 damage (not 5, since it's explosive damage) to the one target and 1.25 to the other 10. So no matter how stacked you are, it only does 2.5 to one muta. So if you have a group of corsairs, it helps to target fire one specific muta even if the mutas are clumped so that you do more concentrated damage on that one muta.
|
Well thats what we were after all along! Thanks Sonko/Stryker! =)
Great write-up btw.
Holy smokas. Valkyries/Corsair being bad vs interceptors is actually mind blowing.
|
Additional details from the Discord discussion when I first approached Sonko about this
Many thanks to PurpleWave and all other participants in the SSCAIT Discord for this. These are things I think that not even progamers and their staff had explored, because they were more worried about outcomes than the internal logic by which these things operate. Valkonic made a splash for a reason. Cheers.
|
Thanks for the mention, all I did was post the source to the openBW source Here you can take a look at it.
|
On January 06 2021 18:05 Sonko wrote:Thanks for the mention, all I did was post the source to the openBW source Here you can take a look at it.
Is that basicially whole BW decomplied?
|
I just wanted to note that "more units mean being more spread out" is not the primary factor in the diminishing returns observed. You'd still see it even if the units were tightly stacked on top of each other. The main factor is the "Full queue" effect which guarantees the missile doing 100% damage to exactly one unit while doing 50%/25% to the rest.
In contrast, ground-based splash does not suffer from diminishing returns when stacked. For example, if a Protoss player were glitching their units into a tight ball to do stacked recall, a siege tank in siege mode could fire a few times and wipe all the units at once, dealing a full 100% damage to every unit. Or if a player drills their workers into one location, a single Reaver scarab would wipe all of them out for 100% damage.
|
Thanks for the investigation. I learned a lot. I always wondered, why Valkyries are bad vs Carriers, because based on the unit stats, half a control group of valkyries should destroy every interceptor of a Carrier fleet in seconds. Little did I know that they just excluded interceptors from taking any splash damage at all...
The developers must have liked Carriers a lot.
|
On January 06 2021 23:07 StRyKeR wrote: I just wanted to note that "more units mean being more spread out" is not the primary factor in the diminishing returns observed. You'd still see it even if the units were tightly stacked on top of each other. The main factor is the "Full queue" effect which guarantees the missile doing 100% damage to exactly one unit while doing 50%/25% to the rest.
In contrast, ground-based splash does not suffer from diminishing returns when stacked. For example, if a Protoss player were glitching their units into a tight ball to do stacked recall, a siege tank in siege mode could fire a few times and wipe all the units at once, dealing a full 100% damage to every unit. Or if a player drills their workers into one location, a single Reaver scarab would wipe all of them out for 100% damage. Ya, and what about Archon vs Muta? They ROAST stacked flocks, can literally kill like 36 stacked mutas in 3 hits no problem.
|
On January 07 2021 05:41 Jealous wrote:Show nested quote +On January 06 2021 23:07 StRyKeR wrote: I just wanted to note that "more units mean being more spread out" is not the primary factor in the diminishing returns observed. You'd still see it even if the units were tightly stacked on top of each other. The main factor is the "Full queue" effect which guarantees the missile doing 100% damage to exactly one unit while doing 50%/25% to the rest.
In contrast, ground-based splash does not suffer from diminishing returns when stacked. For example, if a Protoss player were glitching their units into a tight ball to do stacked recall, a siege tank in siege mode could fire a few times and wipe all the units at once, dealing a full 100% damage to every unit. Or if a player drills their workers into one location, a single Reaver scarab would wipe all of them out for 100% damage. Ya, and what about Archon vs Muta? They ROAST stacked flocks, can literally kill like 36 stacked mutas in 3 hits no problem.
I believe Archon splash isn't considered air splash, so that makes sense as well.
|
Some quick math vs N mutas:
Each missile does 100% of 6/2 = 3 to one muta and a mix of 50%, 25% to all others, let's average to 33%, which is 1 damage. So each missile does 3*1 + 1*(N - 1) = N + 2 damage, or 8N + 16 damage for an entire volley.
To see the diminishing returns, note that we calculate the baseline denominator as 24N, since if we assume each missile does full damage, which is 3, that yields 24 damage per volley and there are N units. So the efficiency rate is (8N + 16)/24N, or 1/3 + 2/(3N). In other words, the efficiency goes down with N, establishing diminishing returns.
N = 1 => 100% N = 2 => 66% N = 3 => 55% N = 4 => 50% and so on.
Furthermore, the average muta takes (8N + 16)/N = 8 + 16/N damage. At 7 mutas, this is roughly 10 damage. In other words, if you have 7 mutas clumped versus a valk, taking a volley is essentially like each muta taking a single hit from a wraith. Taking into account firing speed, the damage output of a valk versus 7 mutas is about 2.5 wraiths. Might be good to keep in mind when calculating whether you should fight or flee with your mutas.
|
On January 06 2021 03:37 StRyKeR wrote: I have an explanation for the single target case. Given what we know about the spaghetti code, my bet is that Blizzard wanted you to be able to attack allies / your own units. But the issue is that valks (like reavers) cannot splash damage your own units. So if only the default splash algorithm is used for the valk, attacking yourself would cause 0 damage since they're splash only, even the intended target. They didn't want that, so they made every missile hit with 100% damage to the target unit regardless of miss. But if they do this with multiple units, valks would nonsensically do 100% to every unit even if miss. So once there's more than one target in the splash zone, they apply regular damage. Sloppy code imo, but consistent with their other sloppy coding.
Doesn't explain the > 100% damage though. Good point, actually. I assume all of you went the route of doing tests on your own Wraiths, using normal melee play and your own Wraiths/Mutas? If so you have to consider that that may completely invalidate results, as far as enemy damage goes. To make sure I'm not just talking out of some false memory, I actually made a test map where I can test it against stacks of enemy and neutral units.
|
On January 07 2021 08:45 StRyKeR wrote: Some quick math vs N mutas:
Each missile does 100% of 6/2 = 3 to one muta and a mix of 50%, 25% to all others, let's average to 33%, which is 1 damage. So each missile does 3*1 + 1*(N - 1) = N + 2 damage, or 8N + 16 damage for an entire volley.
To see the diminishing returns, note that we calculate the baseline denominator as 24N, since if we assume each missile does full damage, which is 3, that yields 24 damage per volley and there are N units. So the efficiency rate is (8N + 16)/24N, or 1/3 + 2/(3N). In other words, the efficiency goes down with N, establishing diminishing returns.
N = 1 => 100% N = 2 => 66% N = 3 => 55% N = 4 => 50% and so on.
Furthermore, the average muta takes (8N + 16)/N = 8 + 16/N damage. At 7 mutas, this is roughly 10 damage. In other words, if you have 7 mutas clumped versus a valk, taking a volley is essentially like each muta taking a single hit from a wraith. Taking into account firing speed, the damage output of a valk versus 7 mutas is about 2.5 wraiths. Might be good to keep in mind when calculating whether you should fight or flee with your mutas.
Thats actually very useful.
|
On January 07 2021 16:23 Freakling wrote:Show nested quote +On January 06 2021 03:37 StRyKeR wrote: I have an explanation for the single target case. Given what we know about the spaghetti code, my bet is that Blizzard wanted you to be able to attack allies / your own units. But the issue is that valks (like reavers) cannot splash damage your own units. So if only the default splash algorithm is used for the valk, attacking yourself would cause 0 damage since they're splash only, even the intended target. They didn't want that, so they made every missile hit with 100% damage to the target unit regardless of miss. But if they do this with multiple units, valks would nonsensically do 100% to every unit even if miss. So once there's more than one target in the splash zone, they apply regular damage. Sloppy code imo, but consistent with their other sloppy coding.
Doesn't explain the > 100% damage though. Good point, actually. I assume all of you went the route of doing tests on your own Wraiths, using normal melee play and your own Wraiths/Mutas? If so you have to consider that that may completely invalidate results, as far as enemy damage goes. To make sure I'm not just talking out of some false memory, I actually made a test map where I can test it against stacks of enemy and neutral units.
Read the first post on second page, I figured it out.
|
Shouldn't make mobile posts while still half asleep in bed in the morning… Also the problem with my own initial test setup was that I did not space out targets fa enough to get real single target damage. The combined spread+splash area of Valkyries is huge (about 5 tiles, it seems, could easily be calculated exactly from the scatter pattern positions and splash radii, of course). However, my statement on the fact that the primary target in a tight stack is always the one to receive the least damage hold empirically true. I'll upload my test map shortly which easily proves this conclusively. Here it is The algorithm, you laid out helps to better zero in on the exact mechanics behind that though: From 14 Missile hits only 5 hit more or less dead-centre, so we can assume that these fully hit the primary target (within 100% splash radius S). However it's probably only to be within 25% radius (L) of all the other missiles, which hit further out. So that gives an estimate of 4/7(5·6 + 9·1.5) ≈ 24, which is pretty accurate, though different, mostly lower values can be observed (it's statistical of course). Getting good estimates for the secondary targets is a lot harder, but it seems that between them "drifting off" into the 50% splash areas of more of the missile explosions and their randomly becoming the primary target of an attack they amass more damage over time than the actual, primary target.
|
The primary target never takes splash damage, so in your example, the other 9 missiles would do 0 splash damage to the primary target, and can only damage it via the random full queue process.
Primary target takes either 0% or 100%. All others either take 25%, 50%, 125%, or 150%.
Say there are N targets total.
If as you said 5 of 14 missiles hit within S of the primary target, that primary target gets 100% and the rest get say, 33% on average.
Primary: 5*100% Other: 5*33%
The other 9 of 14 missiles pick a random primary target, so every target has 1/N chance of 100%. Non-primary takes, say 33% on average again.
Primary: 9/N*100% Other: 9/N*100% + 9*33%
In summary, for 14 missiles, expected damage taken is:
Primary: (5*100% + 9/N*100%)/14 = 5/14 + 9/(N*14) Other: (5*33% + 9/N*100% + 9*33%)/14 = 1/3 + 9/(N*14)
So ultimately, whether primary takes less damage depends on how many hits are straight on and also what the others on average take. Under the assumptions above, since 5/14 < 1/3, the primary target takes less damage.
Note that this estimation doesn't apply to the corsair since its missile always hits the primary target straight on (within S distance), and so the corsair always does more damage to the primary target (100%) as opposed to others (25% or 50%).
|
On January 08 2021 01:18 StRyKeR wrote: The primary target never takes splash damage, so in your example, the other 9 missiles would do 0 splash damage to the primary target, and can only damage it via the random full queue process.
Primary target takes either 0% or 100%. All others either take 25%, 50%, 125%, or 150%. That makes the explanation even more clean-cut.
Say there are N targets total.
If as you said 5 of 14 missiles hit within S of the primary target, that primary target gets 100% and the rest get say, 33% on average.
Primary: 5*100% Other: 5*33%
The other 9 of 14 missiles pick a random primary target, so every target has 1/N chance of 100%. Non-primary takes, say 33% on average again.
Primary: 9/N*100% Other: 9/N*100% + 9*33%
In summary, for 14 missiles, expected damage taken is:
Primary: (5*100% + 9/N*100%)/14 = 5/14 + 9/(N*14) Other: (5*33% + 9/N*100% + 9*33%)/14 = 1/3 + 9/(N*14)
So ultimately, whether primary takes less damage depends on how many hits are straight on and also what the others on average take. Under the assumptions above, since 5/14 < 1/3, the primary target takes less damage.
Note that this estimation doesn't apply to the corsair since its missile always hits the primary target straight on (within S distance), and so the corsair always does more damage to the primary target (100%) as opposed to others (25% or 50%). Imagine a Corsair with Valkyrie scattering on attacks O_o
Would be nice to actually have all the relevant code sequences quoted here for reference…
|
This was linked above: github.com
Just look there -- search for "is_air_splash". I think it's impossible to paste code readably on this forum.
|
On January 08 2021 03:39 StRyKeR wrote:This was linked above: github.comJust look there -- search for "is_air_splash". I think it's impossible to paste code readably on this forum.
You can try like this. Don't Test Me
|
|
Here it is. I'm wondering now if bwapi actually lists which units have air splash. I searched in the repository but couldn't find where such settings are set.
void bullet_deal_splash_damage(bullet_t* b) { auto bb = square_at(b->sprite->position, b->weapon_type->outer_splash_radius); if (is_air_splash) { a_vector<unit_t*> targets; for (unit_t* target : find_units(bb)) { if (target == b->bullet_owner_unit) continue; if (target->owner == b->owner && target != b->bullet_target) continue; if (!weapon_can_target_unit(b->weapon_type, target)) continue; int distance = unit_distance_to(target, b->sprite->position); if (distance > b->weapon_type->outer_splash_radius) continue; if (target == b->bullet_target && distance <= b->weapon_type->inner_splash_radius) { targets = {target}; break; } targets.push_back(target); } if (!targets.empty()) { size_t random_index = 0; if (targets.size() != 1) random_index = lcg_rand(58) % targets.size(); bullet_deal_damage(b, targets[random_index]); } } bool damages_allies = !is_air_splash && b->weapon_type->hit_type != weapon_type_t::hit_type_enemy_splash; for (unit_t* target : find_units(bb)) { if (target == b->bullet_owner_unit && b->weapon_type->id != WeaponTypes::Psionic_Storm) continue; if (!damages_allies && target->owner == b->owner && target != b->bullet_target) continue; if (!weapon_can_target_unit(b->weapon_type, target)) continue; int distance = unit_distance_to(target, b->sprite->position); if (distance > b->weapon_type->outer_splash_radius) continue; if (b->weapon_type->id == WeaponTypes::Psionic_Storm) { if (target->storm_timer) continue; target->storm_timer = 1; } if (is_air_splash) { if (target == b->bullet_target) continue; if (unit_is(target, UnitTypes::Protoss_Interceptor)) continue; } if (!is_air_splash && distance <= b->weapon_type->inner_splash_radius) { bullet_deal_damage(b, target, 1); } else if (!u_burrowed(target)) { if (distance <= b->weapon_type->medium_splash_radius) { bullet_deal_damage(b, target, 2); } else { bullet_deal_damage(b, target, 4); } } } }
|
|
|
|