Physics :D Got to love the quality of our guesstimates.
Checked it all; seemed physically and mathematically correct. Seeing as you edited lately, you might have added something more. As we knew mass would be included again, could the fornula have been simplified further? Since spikes are hitscan, the range for any given spike can be seen as constant, and we can keep distToTarget as it is checked for each spike.
Been pondering about a spreadsheet or plot such as that myself, or something like avarage hits if you fly at someone from range at a certain speed and cone of fire. Would help us loads to get the numbers right in the <i>new approach</i>, wich I love.
Great work! Knowing you right, are you cracking up a nice spreadsheet plot to check out some different values? I think we are so close to an implementation now...
<!--quoteo(post=1892932:date=Jan 7 2012, 10:29 AM:name=Fluid Core)--><div class='quotetop'>QUOTE (Fluid Core @ Jan 7 2012, 10:29 AM) <a href="index.php?act=findpost&pid=1892932"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->Physics :D Got to love the quality of our guesstimates.
Checked it up until kinetic energy, seemed physically and mathematically correct until there atleast. Seeing as you edited lately, you might have added something more. As we knew mass would be included again, could the fornula have been simplified further? Since spikes are hitscan, the range for any given spike can be seen as constant.
Great work! Knowing you right, are you cracking up a nice spreadsheet plot to check out some different values? I think we are so close to an implementation now...
<3<!--QuoteEnd--></div><!--QuoteEEnd--> Haha, actually no, because what ended up happening is that our original kinetic energy approach was re-affirmed as physically valid (the "Fantastic." was actually sarcastic since I realised that I never really needed to do it in the first place :P). The edits actually just made the proof more readable (and corrected one line of the code).
The first time, we used a ratio of an increased kinetic energy (from increased speed) to a base kinetic energy, to determine, essentially, the multiplier for damage, which resulted in damage with respect to total velocity. The second time, I started with the drag force with respect to velocity, determined the deceleration with respect to velocity, determined the velocity with respect to the initial velocity, and then substituted that into the kinetic energy equation (and said that damage is analogous to kinetic energy), which resulted in damage with respect to initial total velocity.
The only difference between "total velocity" and "initial total velocity" is conceptual, but doesn't affect the numbers whatsoever.
<u>Basically</u>, the form of the function is quadratic, EVEN with drag forces considered; and it doesn't "taper off" asymptotically as we hope: with increased speed, you DO get a quadratic increase in kinetic energy; which makes sense really, because the drag force applies over distance (or time) rather than instantaneously modifying velocity (naturally). Kinda sucks when physics doesn't conform to our ideas on game balance :P There's still hope in the average hit-rate / firing cone, and considerations of time to execute, though.
(I mean, we could try again by using the idea of terminal velocity which does taper off asymptotically, but... I don't think that's really valid, because that's based on a balance between drag and buoyancy forces with gravitational force (opposing forces). In the case of a free-flying lerk spike, there aren't any opposing (to each other) forces, only one force: the drag force.)
It wasn't all pointless, though. I guess the only thing that wasn't affirmed as physically valid was the way that the static (or base) damage is calculated.
As you can see, damage = f(movement) * f(distToTarget), or damage = speedDamage * f(distToTarget) i.e. damage is simply: [some function of lerk speed] * [some function of range], or (spikespeed + lerkspeed)^2 * [some function of range] in BOTH cases. The second case is a physically valid approach to drag, while the first case was built on top of the game's original implementation of damage with respect to range.
Whatever these constants are, we can make them up to make up whatever damage we require, since they're just constants - now, they may <u>represent</u>: mass of the spike, drag coefficient of the spike geometry, projected area of the spike, and density of the fluid (air), as well as trauma (damage) with respect to kinetic energy.
When going through the formulas once more I noticed a simplification that may make this less valid. Namely that for v^2=u^2+2ad to be true, constant acceleration is required, and that isn't the case here. Will try to solve it myself, but it'll be a pain to get the integrals right.
Come to the conclusion that I need to solve the differential equation derived from m*dv/dt=-Fd. If I didn't mess up already, that means that the diff eq. would be v'+(rho*Cd*A)/(2m)v^2=0
Hahaha... integrals. I think I'll leave this one in your hands.
By the way, we need to eliminate time as a variable (and replace it with distance), so if you want to get the integral, you'll have to determine acceleration in terms of distance rather than time (ds not dt).** But I have a feeling that when you're dealing with only two points (the start of the flight and the end of the flight), "a" could be considered an "average acceleration" (constant), but I see the wisdom in evaluating it the other way.
** Ummmmm... actually, acceleration is simply a function of velocity: a = -FD / m = constant * v^2, and what else do we know? a = k*v^2 ...how do you integrate that with respect to time? a = k*(x/t)^2 ? v = integral(k*(x/t)^2)dt v = u + k*(-1*x^2/t) ? Now what? We need v, and we want it in terms of u and x without t, so that we can substitute it into the kinetic energy equation. This is why I hate integrals.
There we go, best sleep ever. When I wake up, Yuuki have solved the dif eq. for me :)
So now we can get the speed after a certain distance, and that's the only thing that was left to describe the kinetic energy of the spikes when they had traveled that distance. Now we should be ready to adjust the constants and then test the mod :)
Or simplified when spikes are considered as cylinders to: <b> Damage=KineticenergyToDamage*m/2*(SpikeInitialSpeed+Lerkspeed)^2*e^-(AirDensity*Cd*Distance/(SpikeDensity*CylinderSpikeLength))</b>
That should do it. As for solving the integral, you want to determine the velocity after a certan distance. Tou know the starting velociry and the force acring on the object. But the force depends on the velocity. The result is the dif eq. that Yuuki solved :)
Did a stupid silly misstake, should be correct now.
Constants rho=1.2047 kg/m^3 Talon density=1390 kg/m^3
For mass I'm going to look up horn density , as spikes are probably similar to that. Dividing away mass and V0^2 with KtD, and make it so that d=0 and x=0 give 1 in result. Looking at one plot for varying speed, and another for varying distance.
With Spike radius = 0.5 cm Spike area = 0.7854 cm^2 Spike mean length = 4 cm spike mass = 4.367 g Cd = 1 Spikespeed = 52 m/s
Damage Backward 13 range 20: 36.47% Backward 13 range 0: 56.25% Speed 0 range 20: 64.83% Speed 0 range 0: 100% Speed 13 range 20: 101.30% Speed 13 range 0: 156.25% Speed 20 range 20: 124.30% speed 20 range 0: 191.72%
Great. For some reason most of the parenthesis and other operators is removed... Go to <a href="http://www.wolframalpha.com/" target="_blank">http://www.wolframalpha.com/</a> and add this line: plot (42+x)^2*e^-(1.2047*y/(0.04*1390))/42^2 , -13<x<20, 0<y<20
You should get a 3d plot for y showing distance and x showing speed. 42=SpikeSpeed, 1.0247=AirDensity, 0.04=SpikeLength and 1390=SpikeDensity (taken to be the same material as talons, made of Beta-Keratin).
<!--quoteo(post=1892936:date=Jan 7 2012, 04:35 AM:name=Harimau)--><div class='quotetop'>QUOTE (Harimau @ Jan 7 2012, 04:35 AM) <a href="index.php?act=findpost&pid=1892936"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec--><u>Basically</u>, the form of the function is quadratic, EVEN with drag forces considered; and it doesn't "taper off" asymptotically as we hope: with increased speed, you DO get a quadratic increase in kinetic energy; which makes sense really, because the drag force applies over distance (or time) rather than instantaneously modifying velocity (naturally). Kinda sucks when physics doesn't conform to our ideas on game balance :P There's still hope in the average hit-rate / firing cone, and considerations of time to execute, though.
(I mean, we could try again by using the idea of terminal velocity which does taper off asymptotically, but... I don't think that's really valid, because that's based on a balance between drag and buoyancy forces with gravitational force (opposing forces). In the case of a free-flying lerk spike, there aren't any opposing (to each other) forces, only one force: the drag force.)<!--QuoteEnd--></div><!--QuoteEEnd-->
Yea, terminal velocity wouldn't really apply. So what do you think, should we use the physical approach or the asymptotical one?
Here is the three versions I think we could consider. I'm most happy with the non-physical approach, even though they are more or less physically correct ;)
<b>Physical version with drag to decrease distance damage</b> Damage at 0 range <a href="http://www.wolframalpha.com/input/?i=plot+20%2F47^2*%2847%2Bx%29^2*e^%28-12047*0%2F%281390*0.04%29%29+%2C+-13%3Cx%3C20" target="_blank">http://www.wolframalpha.com/input/?i=plot+...2C+-13%3Cx%3C20</a> Damage decrease with distance: <a href="http://www.wolframalpha.com/input/?i=plot+e^%28-1.2047*x%2F%281390*0.04%29%29+%2C+0%3Cx%3C16" target="_blank">http://www.wolframalpha.com/input/?i=plot+...+%2C+0%3Cx%3C16</a>
<b>Semi-physical approach to drag</b> Damage from 0 to 2 range: <a href="http://www.wolframalpha.com/input/?i=plot+20*%28%281%2F%28abs%28x%29%2F35+%2B+1%29%29*x%2F35+%2B+1%29^2++%2C+-13%3Cx%3C20" target="_blank">http://www.wolframalpha.com/input/?i=plot+...2C+-13%3Cx%3C20</a> Damage decrease from 2 to 16 range: <a href="http://www.wolframalpha.com/input/?i=plot+%2815%2B5*%281-%28x-2%29%2F%2816-2%29%29%29%2F20++%2C+2%3Cx%3C16" target="_blank">http://www.wolframalpha.com/input/?i=plot+...+%2C+2%3Cx%3C16</a> For some reason it didn't work to link the exact code. So I used the exactely same shape but with a few unnecessary things. <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->local damageDistScalar = Clamp(1 - ((distToTarget - kSpikeMaxDamageRange) / (kSpikeMinDamageRange - kSpikeMaxDamageRange)), 0, 1)
local damage = kSpikeMinDamage + damageDistScalar * (kSpikeMaxDamage - kSpikeMinDamage)
//Yuuki: Make damage increase based on your speed //max damage = 20 //min damage = 15 local static = 15 + damageDistScalar * 5 local movement = player:GetVelocity():DotProduct(player:GetViewAngles():GetCoords().zAxis) //spikespeed = 35 local dragfactor = 1/(math.abs(movement)/35 + 1) damage = static * (dragfactor*movement/35 + 1)^2<!--c2--></div><!--ec2-->
// Have damage increase to reward close combat local damageDistScalar = Clamp(1 - ((distToTarget - kSpikeMaxDamageRange) / (kSpikeMinDamageRange - kSpikeMaxDamageRange)), 0, 1) local damage = kSpikeMinDamage + damageDistScalar * (kSpikeMaxDamage - kSpikeMinDamage)
//Yuuki: Make damage increase based on your speed //max damage = 18
local movement = player:GetVelocity():DotProduct(player:GetViewAngles():GetCoords().zAxis) damage = 40/(1+math.exp(-(movement-1)/5))*(0.4*math.exp(-(distToTarget^2)/200)+0.6)<!--c2--></div><!--ec2-->
I did some damage tests against drifters, eggs and MAC:s in summit with the non-physical approach. For drifters (100 health and no armor), I could kill two in one swoop in crevice and have time to avoid contact. Standing on a drifter and shooting a spike, they took 19 damage. Standing on very long range, they took 12 when I hit after 4 spikes or so. When I glided fast against a drifter and shot a spike when close, it did 35 damage. On starting from the ceiling and diving, I managed to reach 37.
On eggs, I tried firing of secondary when close at high speed. They have 250 health and no armor, and took 54% damage in one secondary, that's about 135 damage (compare to 120 up close in live). If not moving but still close, they dealt about 31% (78) damage. For MAC:s, I could often get one kill every two swoops at crevice. This is starting from extreme range and going at max speed at them. Shooting secondary at full speed I managed to reach 27% once, just before I collided with the MAC. They have 325 health and 50 armor.
So this is what I got from Yuuki's integration. Check if you got the same.
>> v = u*e^(-kd) where k = 1/2*rho*Cd*A where u = lerkspeed + spikespeed = initial velocity where v = velocity at distance = d >> KE = m/2*v^2 = m/2*u^2*e^(-2kd) >> damage = a*KE = a*m/2*u^2*e^(-2kd) >> damage = b*(lerkspeed+spikespeed)^2*e^(-2kd) ...(1)
But when d = constant
>> damage = b*(lerkspeed+spikespeed)^2*c
So it's still a quadratic, we've just once again changed how damage changes with distance.
btw, for our purposes we can re-write (1) as >> damage = basedamage/(spikespeed^2)*(lerkspeed+spikespeed)^2*e^(-K*d) K we need to specify on our own for our purposes, for example, with no movement (static), that reduces the equation to >> damage = basedamage*e^(-K*d) At zero distance, >> damage = basedamage*e^(0) so unlike before, our basedamage must be our maximum static damage i.e. >> damage = kSpikeMaxDamage*e^(-K*d) Now we simply have to specify some target. For example Let's do kSpikeMinDamage when the distance is kSpikeMinDamageRange: then >> kSpikeMinDamage = kSpikeMaxDamage*e^(-K*kSpikeMinDamageRange) >> ln(kSpikeMinDamage/kSpikeMaxDamage) = -K*kSpikeMinDamageRange >> K = -ln(kSpikeMinDamage/kSpikeMaxDamage)/kSpikeMinDamageRange For example, kSpikeMinDamage = 15, kSpikeMaxDamage = 20, kSpikeMinDamageRange = 16 >> K = -ln(15/20)/16 = 0.01798 Ideally, we would do this calculation outside of the lerk or spike loop for efficiency, so perhaps in balance.lua, or whatever gets called least. (Or we could simply evaluate it externally, then hard-code it in, and add a comment.)
But of course if we go further back than that range, we reduce our damage, and we don't want to do that. So we can add a clamp to set an upper limit, e.g. >> Clamp(distToTarget, 0, kSpikeMinDamageRange) But I want to specify a maximum damage range as well so: >> Clamp(distToTarget, kSpikeMaxDamageRange, kSpikeMinDamageRange) but now the problem is that at, say, 0 distance, the "distance" actually reads as kSpikeMaxDamageRange = 2 >> damage = kSpikeMaxDamage*e^(-K*2) >> damage < kSpikeMaxDamage so we no longer actually do maximum damage as we wish, so instead, we subtract kSpikeMaxDamageRange from the evaluation and the upper and lower limits: >> Clamp(distToTarget-kSpikeMaxDamageRange, kSpikeMaxDamageRange-kSpikeMaxDamageRange, kSpikeMinDamageRange-kSpikeMaxDamageRange) or simply: >> Clamp(distToTarget - kSpikeMaxDamageRange, 0, kSpikeMinDamageRange - kSpikeMaxDamageRange) Now >> damage(d=2) = kSpikeMaxDamage*e^(-K*(2-2)) = kSpikeMaxDamage >> damage(d=1) = kSpikeMaxDamage*e^(-K*("0")) = kSpikeMaxDamage >> damage(d=16) = kSpikeMaxDamage*^(-K*(14)) > kSpikeMinDamage of course, now we realise that now we need to change K as well: >> K = -ln(kSpikeMinDamage/kSpikeMaxDamage)/(kSpikeMinDamageRange-kSpikeMaxDamageRange) e.g. kSpikeMinDamage = 15, kSpikeMaxDamage = 20, kSpikeMinDamageRange = 16, kSpikeMaxDamageRange = 2 >> K = -ln(15/20)/(16-2) = 0.020548719 Now >> damage(d=1) = kSpikeMaxDamage*(e^(-K*("0")) = kSpikeMaxDamage >> damage(d=17) = kSpikeMaxDamage*(e^(-K*("14")) = kSpikeMinDamage
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->// local distToTarget = ~ // local movement = ~
Looking at the graph like that, it's practically linear...
<!--quoteo(post=1893108:date=Jan 8 2012, 01:46 PM:name=Fluid Core)--><div class='quotetop'>QUOTE (Fluid Core @ Jan 8 2012, 01:46 PM) <a href="index.php?act=findpost&pid=1893108"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->I did some damage tests against drifters, eggs and MAC:s in summit with the non-physical approach. For drifters (100 health and no armor), I could kill two in one swoop in crevice and have time to avoid contact. Standing on a drifter and shooting a spike, they took 19 damage. Standing on very long range, they took 12 when I hit after 4 spikes or so. When I glided fast against a drifter and shot a spike when close, it did 35 damage. On starting from the ceiling and diving, I managed to reach 37.
On eggs, I tried firing of secondary when close at high speed. They have 250 health and no armor, and took 54% damage in one secondary, that's about 135 damage (compare to 120 up close in live). If not moving but still close, they dealt about 31% (78) damage. For MAC:s, I could often get one kill every two swoops at crevice. This is starting from extreme range and going at max speed at them. Shooting secondary at full speed I managed to reach 27% once, just before I collided with the MAC. They have 325 health and 50 armor.<!--QuoteEnd--></div><!--QuoteEEnd--> ...wow.
Makes me think we may need to just clamp damage...
Alternatively... we could play around with trying to give the lerk a terminal velocity...
EDIT: Actually, the base damage just seems too high. I mean, in one second, the lerk can deal 12.5 * 20 while at point-blank and standing still. 250 damage per second... seriously?
Let's see... the rifle does 180 damage per second, but you're more likely to miss. I'm thinking we just need to drop the max/min damage range of the lerk spike (so 10~15 for instance, or even less); OR reduce the rate of fire of primary and reduce the number of spikes for secondary, but that way is much less flexible and more punishing.
Changed the constants some, and multiplied the damage by 12.5 to get damage per second:
<!--quoteo(post=1893109:date=Jan 8 2012, 06:47 AM:name=Harimau)--><div class='quotetop'>QUOTE (Harimau @ Jan 8 2012, 06:47 AM) <a href="index.php?act=findpost&pid=1893109"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->Haven't read through the thread yet, but got any graphs?<!--QuoteEnd--></div><!--QuoteEEnd--> Check my post two above yours, the one with three blocks of code and six links. The links go to the damage graphs and the range scaling.
The models shown are the completely physical approach, the approach you said you prefered, and the sigmoid shape I liled.
<b>Live Damage Comparsion</b> Up close: 24 damage each spike=300 dps From range: 20 damage each spike=250 dps
<b>Sigmoid Damage Comparsion</b> Up close static: 18 damage each spike=225 dps From range static: 10.8 damage (11?) each spike=135 (137.5) dps
That is assuming perfect hit ratio. Up close all should hit, while for range, they got a eight degree cone insteaad of six like us, so they would miss more. Your circle-rectangle overlap spreadsheet would be gold here. But I saw your point and actually decreased the sigmoid shaped damage to 18 static, while adjusting the slope slightly, and still reach about 36 at 13 speed and about 39 at 20 speed. I think that's warranted considering the short time frame during a swoop, and that the static is reduced to 2/3 of the live one.
Oh, and the formula you got seems to be about the same as what I got, but I inserted the actual constants, so I can't see right of the bat.
The completely physical approach with drag. <a href="http://dl.dropbox.com/u/49503284/lerkSpikes/lua/Weapons/Alien/SpikesPhysical.lua" target="_blank">http://dl.dropbox.com/u/49503284/lerkSpike...kesPhysical.lua</a> The semi physical approach with drag for speed damage decrease, but linear damage decrease for range. <a href="http://dl.dropbox.com/u/49503284/lerkSpikes/lua/Weapons/Alien/SpikesSemiPhysical.lua" target="_blank">http://dl.dropbox.com/u/49503284/lerkSpike...emiPhysical.lua</a> The mathematical approach with a sigmoid shape. The one I tested against eggs/drifters/MAC:s <a href="http://dl.dropbox.com/u/49503284/lerkSpikes/lua/Weapons/Alien/SpikesSigmoid.lua" target="_blank">http://dl.dropbox.com/u/49503284/lerkSpike...ikesSigmoid.lua</a>
I'm sorry but I don't have the time for follow you guys up on all the detailed numbers and graphs. Looks like you are past the "initial idea"-stage and into the "balancing the numbers"-stage. Can you give a quick summary of what mechanics made it into your final version and try to describe the impact it will have on lerkplay?
The only real mechanic change is to increade your spike damage with forward speed. Strafing doesn't affect the damage, while moving backward decrease it. We are also looking into how range should decrease the damage of the spikes.
The goal of these changes is to change the focus of lerk gameplay away from getting close to a marine and circle them by the roof. Instead we want the lerks to fly in against a marine and then fall back for a new attack . By this change lerks could do a fair bit of damage on the initial charge, and then finnish it of with low damage spikes or take evasive action and prepare for a new attack. At the same time, we want to make the lerks less effective at sieging from afar, and the combination of lower damage while stationary and damage decrease with rsnge, they should want to get close or atleast fly in and out quickly to take down structures. All this is meant to get the lerk effectively use the ability to fly in combat. To change them from gettinh into your fsce, and staying there, to fly in and out and be the mobile class they should be.
I think this summarize our intents and idea. If i forgot something or made a misstake, please point it out so that I can correct it.
I did the derivation, and it led to a quadratic function vs movement and an exponential decay function vs distance. And looking at yours, it looks practically the same, but you've tried to simulate the actual values by substituting some values into the equation; I, on the other hand, have gathered the constants and done it from the perspective of specifying known game values, and determining the required constants - the derivation of which you can see above. It works well because we can specify anything we like, of the following constants: <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->kSpikeMaxDamage 20 kSpikeMinDamage 15 kSpikeMaxDamageRange 2 kSpikeMinDamageRange 16 spikeSpeed 52 distConstant 0.020548719 // (calculated)<!--c2--></div><!--ec2-->
and then get the required damage such that: <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->damage = kSpikeMaxDamage*(movement/spikeSpeed+1)^2*math.exp^(-distConstant*distLimited))<!--c2--></div><!--ec2--> where <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->distConstant = -math.ln(kSpikeMinDamage / kSpikeMaxDamage) / (kSpikeMinDamageRange - kSpikeMaxDamageRange) local distLimited = Clamp(distToTarget - kSpikeMaxDamageRange, 0, kSpikeMinDamageRange - kSpikeMaxDamageRange)<!--c2--></div><!--ec2-->
1) when movement = 0, distToTarget <= kSpikeMaxDamageRange, damage = kSpikeMaxDamage 2) when movement > 0, distToTarget <= kSpikeMaxDamageRange, damage > kSpikeMaxDamage * 3) when movement < 0, distToTarget <= kSpikeMaxDamageRange, damage < kSpikeMaxDamage *
4) when movement = 0, kSpikeMaxDamageRange < distToTarget < kSpikeMinDamageRange, kSpikeMaxDamage > damage > kSpikeMaxDamage 5) when movement != 0, kSpikeMaxDamageRange < distToTarget < kSpikeMinDamageRange, damage = f(movement, distToTarget)
6) when movement = 0, distToTarget >= kSpikeMinDamageRange, damage = kSpikeMinDamage 7) when movement > 0, distToTarget >= kSpikeMinDamageRange, damage > kSpikeMinDamage * 8) when movement < 0, distToTarget >= kSpikeMinDamageRange, damage < kSpikeMinDamage *
* the lower the spikeSpeed, the greater the degree of difference from the "base" values that increased or decreased movement induces.
Even though this is technically the physical approach, it makes balancing the numbers a very simple affair, since we know exactly what the numbers will come out to be. The work's been done in the excel spreadsheet, you just need to plug in the numbers.
The disadvantage with the physical approach is that when you have incredibly high movement (which you obtain when falling), you get incredibly high damage per spike. Although this could be realistic, it isn't so conducive to game balance. However, this damage is mitigated by at least two other factors: Less time to execute, and higher chance of missing. (I guess I'll get onto that hit-rate spreadsheet today.) However, <u>if we need to</u> (the two factors above are not sufficient), a way to "hack" a solution to this is to do something like: <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->local movement = ~ length(movement) = length(movement)^0.9 // "length" since movement can be positive or negative, and we only want to change the size of the movement, // while trying to raise a negative number to a fractional power would be problematic. // if length() doesn't work, can possibly be done using movement = sign(movement)*abs(movement)^0.9 // assuming there's a math.sign function (returns -1 if -ve, 0 if zero, 1 if +ve) damage = ~<!--c2--></div><!--ec2--> (It doesn't have to be 0.9, it can be less or more, but less than 1 of course.) Basically, the size of movement is reduced and reduced proportionally to its own size, so increased movement does not lead to such an increased damage. Illustration: <img src="http://i.imgur.com/b9I32.png" border="0" class="linked-image" />
<u>A note on the sigmoid function</u>: I find that the sigmoid function is incredibly difficult to understand, and would require a whole lot of trial and error to change the hard-coded numbers and get good values. Do you think you could re-write it using soft-coded constants (like what I have above)? The goals would be the points 1) to 8) that I've described above (minimum static damage at minimum range, maximum static damage at maximum range, etc.; write equations in terms of spikespeed, and maximum and minimum damage and range).
The only real mechanic change is to increade your spike damage with forward speed. Strafing doesn't affect the damage, while moving backward decrease it. We are also looking into how range should decrease the damage of the spikes.
The goal of these changes is to change the focus of lerk gameplay away from getting close to a marine and circle them by the roof. Instead we want the lerks to fly in against a marine and then fall back for a new attack . By this change lerks could do a fair bit of damage on the initial charge, and then finnish it of with low damage spikes or take evasive action and prepare for a new attack. At the same time, we want to make the lerks less effective at sieging from afar, and the combination of lower damage while stationary and damage decrease with rsnge, they should want to get close or atleast fly in and out quickly to take down structures. All this is meant to get the lerk effectively use the ability to fly in combat. To change them from gettinh into your fsce, and staying there, to fly in and out and be the mobile class they should be.
I think this summarize our intents and idea. If i forgot something or made a misstake, please point it out so that I can correct it.<!--QuoteEnd--></div><!--QuoteEEnd--> Yep, basically this.
Another way to look at it is simply, damage increases with forward speed. <!--coloro:gray--><span style="color:gray"><!--/coloro--><!--sizeo:1--><span style="font-size:8pt;line-height:100%"><!--/sizeo-->based on the concepts of kinetic energy, relative velocities or conservation of momentum<!--sizec--></span><!--/sizec--><!--colorc--></span><!--/colorc--> We also want damage to decrease with distance. <!--coloro:gray--><span style="color:gray"><!--/coloro--><!--sizeo:1--><span style="font-size:8pt;line-height:100%"><!--/sizeo-->based on the concept of drag<!--sizec--></span><!--/sizec--><!--colorc--></span><!--/colorc-->
The lerk will want to <b>move</b>, play aggressively, and swoop down on targets. The lerk will be Death From Above.
Other implications that Fluid Core hasn't yet definitively described: - Lerks that fly backwards while shooting will be penalised (do a bit less damage than normal) for attacking while retreating, making it fairer for marines (who are also penalised for the same by having reduced back-walk speed). - Circling (strafe-flight) or hovering around a target, or sitting still and firing, will not have any damage bonus. - An implication for the marines is that, because swooping is significantly more effective (you can gather a lot of speed and therefore damage), marines will want to "watch the skies" a bit more when they know lerks are in play and there are high ceilings. A further implication would be that this splits their attention away from the walls and ground where skulks may be, making skulks a little more effective. Or the reverse, skulks in play draws attention to the wall and ground, leaving the lerk open to safely rain death from above. and that's it, I think.
Oh, and Fluid Core: I just thought of a third factor that would affect the damage when swooping: seen from above, a marine's hitbox is significantly smaller than when seen from the side, at the same distance - more difficult to aim AND even with perfect aim, a lower hit-rate. I'm just a little worried about the secondary (striking at the last second). It might make sense to reduce the damage for secondary (from a realism perspective, there's less force available per spike per shot).
Started the computer while standing in the cold night air and looking at the full moon on the clear night sky. Here we go again.
@Harimau: The approach that I call sigmoid shaped actually consists of two functions; the damage from speed shaped from the sigmoid, while the damage decrease from distance is based on the shape of the normal-distribution. Let's first look at the sigmoid function and I'll show how I reached all the values, while after that looking at the range function.
Before we start with the values, you need to understand how it behaves. When x approaches -infinite the value of the sigmoid function approaches 0, when x is 0 the value of the function is 1/2, and when x approaches +infinite the function approaches 1. Now we are ready to start.
1) By multiplying the function with a positive value, we change the magnitude at each point.
My initial approach was to regard this multiplier at 2*StaticDamage*, but it is more clear to view it as MaximumDamagePotential for the final function. I will explain why at the third stage.
2) By dividing the variable we reduce the slope of the curve. This was done so that we wouldn't reach withing maybe 1 of the MaximumDamagePotential within speeds as low as 4.
The only purpose of that value is to change how fast we reach a certain damage.
3) By adding a constant value in the exponent, we offset the entire function left or (like here) right, so that the damage get lower at the same speed.
As you pointed out, the static damage was quite high, and I wanted to reduce it. I was happy with the MaximumDamagePotential however. There are multiple approaches to change the static damage. Since I was happy with the maximum and not to concerned with low damage while moving backward, I choose the approach that I felt would be most computer optimized. The value here may seem to be chosen arbitrary. It is not. I choose the static damage I wanted (18) and found out where the function had that value.
By a happy coincidence -5ln(11/9)=-1.003353, and I saw no reason to use up extra computational resources to keep it that exact. So I set it to -1 instead. With that the damage as a function of speed is done.
Now we move on to the High-Low function. In basic form it looks like:
We will just look at positive values of x, since the range will always be positive. This function starts of from 1 at zero range, and then starts of slowly decreasing, the slope getting steadily steeper before it starts to pan out towards zero at larger values.
1) By dividing the distToTarget with a positive value (>1) we make the curve less steep.
The first thing we notice is that this happens very quickly. To reduce this speed we again divide in the exponent. I choose to divide after the speed had been squared, since I think the computer has easier to handle divisions then squaring very small numbers (as we can get at low distances). This value changes how fast we get close to the minimum damage.
2) By adding a constant after the function, we set a cap to how low the function can get.
<i>*Actual Damage depends on the StaticDamageAdjustment. When it is ~=-1 then 1/2MaximumDamagePotential*~=18</i>
@Insane Secondary damage: I don't think that it's viable to balance the secondary for the extreme case of high ceiling and firing right up close in a dive. That will make the secondary useless in all other situations.
Haven't read your post yet, just got a quick question about something else. Is the size of the firing cone the angle from the centre of the firing cone, or the total angle (i.e. twice the angle from the centre of the firing cone)? I've just finished the hit-rate spreadsheet, but just need confirmation of that one step. In my spreadsheet I've assumed that the firing cone (6 degrees, 8 degrees) is the angle from the centre of the firing cone. It's a trivial change if it's the other way, the total angle (i.e. I just create a new cell for the firing cone total angle, and the cell for the angle I've used for calculations gets changed to equal to half of that).
It actually comes out really nicely. There are three "break points" (when the hit-rate takes a sudden decline), the first one is extremely visible, the second is less visible, and the third one isn't really visible at all because the region between the second and third break points is always so small. Here's a preview: <img src="http://i.imgur.com/Zzd11.png" border="0" class="linked-image" /> I've used hitbox height = 1.8, width = 0.5, firing cone angle (from centre) = 6 degrees, over a range of 0~30 with increments of 0.1.
Also, by duplicating sheets, changing the cone and merging graphs: <img src="http://i.imgur.com/1M8Gd.png" border="0" class="linked-image" />
It must be the angle you assumed. Oh, and I've just been assuming that 6 is degrees... That seemed reasonable.
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->local kSpread = Math.Radians(6) // Calculate spread for each shot, in case they differ local randomAngle = NetworkRandom() * math.pi * 2 local randomRadius = NetworkRandom() * NetworkRandom() * math.tan(kSpread) local spreadDirection = (viewCoords.xAxis * math.cos(randomAngle) + viewCoords.yAxis * math.sin(randomAngle))<!--c2--></div><!--ec2-->
You need to take account of the random distribution though. Most likely 0<NetworkRandom()<1 and is a linear distribution. spreadDirection can probably be ignored with enough iterations and just rotate the linear spread over the disk.
Well I'm assuming it is evenly distributed, given enough hits. That's why it's theoretical. Like, if you roll a die 6,000 times, you'd most likely roll "6" around 1,000 times. So I can effectively take randomness out of the equation, which I have. The approach I've taken simply measures the area of overlap between the circular area of the firing cone and the rectangular target's hitbox, and divides that by the circular area of the firing cone. The only real effort I've gone to is in expressing that mathematically, so that I can get instant results in a spreadsheet. I haven't actually created a system that "practically" rather than "theoretically" expresses it by randomising points for each distance 1000 times or something; hell, I wouldn't even know where to start with that.
I've only really roughly formatted the spreadsheet, but here it is: <a href="http://www.mediafire.com/?4olkffvl27p6ldb" target="_blank">Mediafire</a> You can pretty much delete all but the first sheet, but the others have some interesting stuff on them.
This is how I brainstormed and planned it: <img src="http://i.imgur.com/G4Nae.png" border="0" class="linked-image" /> This picture also illustrates why the hit-rate has "break points".
Some assumptions: - Rectangular hitbox - Aiming at the centre of the hitbox (optimal condition) - Height of the hitbox is greater than its width (not really important... it's all relative, you can make it anything, the math just operates under that assumption) - Linear (even) distribution of shots around the centre in a circular disc - other stuff I forget, maybe
Now the next step is to take our various damage models and merge them into the document: multiply the theoretical hit-rate by the raw damage per spike (or damage per second), to get the theoretical average damage per spike (or damage per second).
The angle control the standard deviation of the distribution of the spread, which is an uniform product distribution (multiplication of two uniform variables) :
I'm not sure how you should interpret it, the angle is also the maximum spread possible and thus defines a cone, but the probability to have a projectile close to this cone is very low.
And that's the histogram for the squared uniform distribution. Again 100000 points. <!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->clear random=rand(100000,3); %uniform distribution % kSpread = Math.Radians(6); % Calculate spread for each shot, in case they differ values=zeros(100000,2); values(:,1) = random(:,1) .* pi() * 2; %angle values(:,2) = random(:,2) .* random(:,3); %radius hist(values(:,2),10000)
(You know what I hate more than integration? Statistics.)
Ah, well that's no good at all then - so with the model they've used, shots essentially tend towards the centre? I did not know that. Is there a simple way to interpret that for our purposes? Like an approximate function that describes the distribution as a curve (number of shots vs radius)... projected onto the cone's circle... then integrating to find the volume of the projection over the overlap, divided by the volume of the projection over the circle? Integration in three dimensions? I've never even considered it. Is it even viable to do it analytically, as I've done it in two dimensions?
To illustrate, something like this: <img src="http://i.imgur.com/QwaE3.png" border="0" class="linked-image" />
<!--quoteo(post=1893192:date=Jan 9 2012, 11:16 AM:name=Fluid Core)--><div class='quotetop'>QUOTE (Fluid Core @ Jan 9 2012, 11:16 AM) <a href="index.php?act=findpost&pid=1893192"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->3) By adding a constant value in the exponent, we offset the entire function left or (like here) right, so that the damage get lower at the same speed.
As you pointed out, the static damage was quite high, and I wanted to reduce it. I was happy with the MaximumDamagePotential however. There are multiple approaches to change the static damage. Since I was happy with the maximum and not to concerned with low damage while moving backward, I choose the approach that I felt would be most computer optimized. The value here may seem to be chosen arbitrary. It is not. I choose the static damage I wanted (18) and found out where the function had that value.
By a happy coincidence -5ln(11/9)=-1.003353, and I saw no reason to use up extra computational resources to keep it that exact. So I set it to -1 instead. With that the damage as a function of speed is done.<!--QuoteEnd--></div><!--QuoteEEnd--> Am I right in thinking that the bottom block should actually have the unknown read StaticDamageAdjustment, rather than Lerkspeed? Since Lerkspeed is a variable.
I read through it and the approach and justifications seem pretty reasonable. It does seem somewhat complex for what it does though; is there a way to benchmark the speed of execution? I guess the other, well I won't really call it an issue as such, is that it doesn't conform to reality, it just follows a certain shape... which is fine, really, since we need to mitigate those edge cases that come with high ceilings, which this approach does well.
Regarding the secondary, in what situations is it actually used though? If we do go with a non-sigmoid approach, I feel that the secondary is too powerful at point blank when dropping from a height.
>I did not know that. Is there a simple way to interpret that for our purposes? Like an approximate function that describes the distribution as a curve (number of shots vs radius)...
According to my rather complicated calculations (i.e. maybe wrong) it goes like that. The shots at distance d cover a disc of radius rs = d tan(spreadAngle), the area of the target exposed is approximated as a disc with radius rt.
Then for rt < rs the fraction of spikes that hit on average is given by :
<!--quoteo(post=1893219:date=Jan 9 2012, 02:22 PM:name=Harimau)--><div class='quotetop'>QUOTE (Harimau @ Jan 9 2012, 02:22 PM) <a href="index.php?act=findpost&pid=1893219"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->(You know what I hate more than integration? Statistics.)
Ah, well that's no good at all then - so with the model they've used, shots essentially tend towards the centre? I did not know that. Is there a simple way to interpret that for our purposes? Like an approximate function that describes the distribution as a curve (number of shots vs radius)... projected onto the cone's circle... then integrating to find the volume of the projection over the overlap, divided by the volume of the projection over the circle? Integration in three dimensions? I've never even considered it. Is it even viable to do it analytically, as I've done it in two dimensions?
To illustrate, something like this: <img src="http://i.imgur.com/QwaE3.png" border="0" class="linked-image" /><!--QuoteEnd--></div><!--QuoteEEnd--> I should be able to get the 2-d shape of the curve (the histogram from center edge). Unfortunately I can't recall how to do it analytically, and couldn't fint it on the web. I will try to do it numerically instead :) I'm not sure how to continue from that though... I mean, then we rotate the function around and get the volume, but what do we do with that? It is possible to do 3-dim integrals atleast.
<!--quoteo(post=1893219:date=Jan 9 2012, 02:22 PM:name=Harimau)--><div class='quotetop'>QUOTE (Harimau @ Jan 9 2012, 02:22 PM) <a href="index.php?act=findpost&pid=1893219"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->Am I right in thinking that the bottom block should actually have the unknown read StaticDamageAdjustment, rather than Lerkspeed? Since Lerkspeed is a variable.<!--QuoteEnd--></div><!--QuoteEEnd--> No, it should be LerkSpeed. What I'm trying to do there is to find out at what speed the damage would be 18 instead of 20, and then substitute that speed into the function to shift the entire thing. Compare it to the function y=x. If I want to get y=1 when x=0, then I change the function to y=(x+1)
<!--quoteo(post=1893219:date=Jan 9 2012, 02:22 PM:name=Harimau)--><div class='quotetop'>QUOTE (Harimau @ Jan 9 2012, 02:22 PM) <a href="index.php?act=findpost&pid=1893219"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->I read through it and the approach and justifications seem pretty reasonable. It does seem somewhat complex for what it does though; is there a way to benchmark the speed of execution? I guess the other, well I won't really call it an issue as such, is that it doesn't conform to reality, it just follows a certain shape... which is fine, really, since we need to mitigate those edge cases that come with high ceilings, which this approach does well.
Regarding the secondary, in what situations is it actually used though? If we do go with a non-sigmoid approach, I feel that the secondary is too powerful at point blank when dropping from a height.<!--QuoteEnd--></div><!--QuoteEEnd-->
Do you mean at what speed a certain damage is reached, or regular attacking speeds of a lerk? From my experience you quickly reach 13 speed, with a few flaps. 13 speed should give about 36 (0 range) damage with the current numbers.
@Yuuki, I was actually hoping more for an equation that approximated the histogram that Fluid Core posted, that I could use for an integration, to determine the volumes above the disc and above the overlap. But even with that, I haven't got a clue how to do the integration, to be honest. I mean essentially you have to integrate with respect to two dimensions... numerical integration is more viable, but more time consuming. Creating a matlab script or program might be more worthwhile in that case. With the plot that you just posted, does that take into account both conditions - the area of the target exposed, as well as the distribution of spikes? What are the axes? I'm also not sure about the approximation of the hitbox as a disc - I'm not sure what dimensions would be analogous to these radii. I used the approximation of a rectangle because historically hitboxes have been rectangular prisms - I'm not too sure what approach NS2 takes.
<!--quoteo(post=1893222:date=Jan 9 2012, 10:03 PM:name=Fluid Core)--><div class='quotetop'>QUOTE (Fluid Core @ Jan 9 2012, 10:03 PM) <a href="index.php?act=findpost&pid=1893222"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->No, it should be LerkSpeed. What I'm trying to do there is to find out at what speed the damage would be 18 instead of 20, and then substitute that speed into the function to shift the entire thing. Compare it to the function y=x. If I want to get y=1 when x=0, then I change the function to y=(x+1)<!--QuoteEnd--></div><!--QuoteEEnd--> Hmm :/ it's just that you've already stated the shift (StaticDamageAdjustment) before you worked out the lerkspeed. I mean, personally I would have just said damage = 18 when lerkspeed = 0, which makes more sense to me on a fundamental level (static => lerkspeed = 0). To use your analogy, I would have just written it as y(0) = 1 = (0 + b); then evaluated for b. It should lead to the same result, just a difference in our reasoning/methodology I guess.
By speed of execution, I meant the game code - basically we don't wanna bog down the server with complicated math.
I guess, in all aspects, especially balance, it just has to be tested. My current preference is the physical approach I last described, though.
Basically the fraction grows faster than linear with the radii ratio, because the spikes are centered.
I don't think it's really useful to go into more details, all this is already an overkill, it would be much more simple to jump in game and to tweak the values :)
I'm getting nowhere here. I mean, sure I can get the number of events in the histogram easily, but I can't get it to become well fitting function. Then again, I just know how to do polyfit in matlab...
<!--quoteo(post=1893233:date=Jan 9 2012, 04:29 PM:name=Yuuki)--><div class='quotetop'>QUOTE (Yuuki @ Jan 9 2012, 04:29 PM) <a href="index.php?act=findpost&pid=1893233"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->The distribution is given here (red curve):
I don't think it's really useful to go into more details, all this is already an overkill, it would be much more simple to jump in game and to tweak the values :)<!--QuoteEnd--></div><!--QuoteEEnd-->
SWEET!
Second link is broken, can you link the function instead? How did you manage to find it?! Wish I was better at integrals really... They are crazy useful.
What are we waiting for? Set up a server and let's go!
Comments
Got to love the quality of our guesstimates.
Checked it all; seemed physically and mathematically correct. Seeing as you edited lately, you might have added something more. As we knew mass would be included again, could the fornula have been simplified further?
Since spikes are hitscan, the range for any given spike can be seen as constant, and we can keep distToTarget as it is checked for each spike.
Been pondering about a spreadsheet or plot such as that myself, or something like avarage hits if you fly at someone from range at a certain speed and cone of fire. Would help us loads to get the numbers right in the <i>new approach</i>, wich I love.
Great work! Knowing you right, are you cracking up a nice spreadsheet plot to check out some different values? I think we are so close to an implementation now...
<3
Got to love the quality of our guesstimates.
Checked it up until kinetic energy, seemed physically and mathematically correct until there atleast. Seeing as you edited lately, you might have added something more. As we knew mass would be included again, could the fornula have been simplified further?
Since spikes are hitscan, the range for any given spike can be seen as constant.
Great work! Knowing you right, are you cracking up a nice spreadsheet plot to check out some different values? I think we are so close to an implementation now...
<3<!--QuoteEnd--></div><!--QuoteEEnd-->
Haha, actually no, because what ended up happening is that our original kinetic energy approach was re-affirmed as physically valid (the "Fantastic." was actually sarcastic since I realised that I never really needed to do it in the first place :P). The edits actually just made the proof more readable (and corrected one line of the code).
The first time, we used a ratio of an increased kinetic energy (from increased speed) to a base kinetic energy, to determine, essentially, the multiplier for damage, which resulted in damage with respect to total velocity.
The second time, I started with the drag force with respect to velocity, determined the deceleration with respect to velocity, determined the velocity with respect to the initial velocity, and then substituted that into the kinetic energy equation (and said that damage is analogous to kinetic energy), which resulted in damage with respect to initial total velocity.
The only difference between "total velocity" and "initial total velocity" is conceptual, but doesn't affect the numbers whatsoever.
<u>Basically</u>, the form of the function is quadratic, EVEN with drag forces considered; and it doesn't "taper off" asymptotically as we hope: with increased speed, you DO get a quadratic increase in kinetic energy; which makes sense really, because the drag force applies over distance (or time) rather than instantaneously modifying velocity (naturally). Kinda sucks when physics doesn't conform to our ideas on game balance :P There's still hope in the average hit-rate / firing cone, and considerations of time to execute, though.
(I mean, we could try again by using the idea of terminal velocity which does taper off asymptotically, but... I don't think that's really valid, because that's based on a balance between drag and buoyancy forces with gravitational force (opposing forces). In the case of a free-flying lerk spike, there aren't any opposing (to each other) forces, only one force: the drag force.)
It wasn't all pointless, though. I guess the only thing that wasn't affirmed as physically valid was the way that the static (or base) damage is calculated.
For comparison,
Before:
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->//* local damageDistScalar = Clamp(1 - (distToTarget / kSpikeMinDamageRange), 0, 1)
//* local static = 15 + damageDistScalar * 5
//* damage = ((73+movement)^2)/(73^2)*static
//*> spikespeed = 73
// damage = ((73+movement)^2)/(73^2)*(15 + damageDistScalar * 5)
damage = ((73+movement)^2)/(73^2)*(15 + Clamp(1 - (distToTarget / kSpikeMinDamageRange), 0, 1) * 5)
// isolating (73+movement)^2, calling it speedDamage:
damage = speedDamage/(73^2)*(15 + Clamp(1 - (distToTarget / kSpikeMinDamageRange), 0, 1) * 5)
// for ease of understanding, removing the clamp,
//> replacing with just the evaluation (1-distToTarget/kSpikeMinDamageRange)
//> i.e. assuming distToTarget < kSpikeMinDamageRange
damage = speedDamage/(73^2)*(15 + 5*(1 - (distToTarget / kSpikeMinDamageRange)))
// gathering, renaming constants:
damage = speedDamage * CONSTANTA*(CONSTANTB + CONSTANTC*(1 - (distToTarget / CONSTANTD)))<!--c2--></div><!--ec2-->
After:
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->// damage = CONSTANT3*(lerkspeed+spikespeed)^2/(1+CONSTANT2*distToTarget)
damage = CONSTANT3*(73+movement)^2/(1+CONSTANT2*distToTarget)
// isolating (73+movement)^2, calling it speedDamage:
damage = speedDamage * CONSTANT3/(1+CONSTANT2*distToTarget)<!--c2--></div><!--ec2-->
As you can see, damage = f(movement) * f(distToTarget), or damage = speedDamage * f(distToTarget)
i.e. damage is simply: [some function of lerk speed] * [some function of range], or (spikespeed + lerkspeed)^2 * [some function of range]
in BOTH cases.
The second case is a physically valid approach to drag, while the first case was built on top of the game's original implementation of damage with respect to range.
Whatever these constants are, we can make them up to make up whatever damage we require, since they're just constants - now, they may <u>represent</u>: mass of the spike, drag coefficient of the spike geometry, projected area of the spike, and density of the fluid (air), as well as trauma (damage) with respect to kinetic energy.
Anyway, I better have breakfast before I return.
If interested, your full formula was;
Damage=KineticToDamageComponent/2*m^2/(m+rho*Cd*s) * (LerkSpeed+InitialSpikeSpeed)^2
Come to the conclusion that I need to solve the differential equation derived from m*dv/dt=-Fd. If I didn't mess up already, that means that the diff eq. would be v'+(rho*Cd*A)/(2m)v^2=0
I'll continue after I've got some more sleep.
By the way, we need to eliminate time as a variable (and replace it with distance), so if you want to get the integral, you'll have to determine acceleration in terms of distance rather than time (ds not dt).** But I have a feeling that when you're dealing with only two points (the start of the flight and the end of the flight), "a" could be considered an "average acceleration" (constant), but I see the wisdom in evaluating it the other way.
** Ummmmm... actually, acceleration is simply a function of velocity: a = -FD / m = constant * v^2, and what else do we know?
a = k*v^2
...how do you integrate that with respect to time?
a = k*(x/t)^2 ?
v = integral(k*(x/t)^2)dt
v = u + k*(-1*x^2/t) ?
Now what? We need v, and we want it in terms of u and x without t, so that we can substitute it into the kinetic energy equation.
This is why I hate integrals.
<img src="http://i.imgur.com/yV3aA.png" border="0" class="linked-image" />
Latex source :
<a href="http://sciencesoft.at/image/latex/latex.txt?archive=2861fe4a32bd614b4bce2a451344af40c267c33.txt" target="_blank">http://sciencesoft.at/image/latex/latex.tx...af40c267c33.txt</a>
<a href="http://www.sciencesoft.at/latex/?lang=en" target="_blank">http://www.sciencesoft.at/latex/?lang=en</a>
So now we can get the speed after a certain distance, and that's the only thing that was left to describe the kinetic energy of the spikes when they had traveled that distance. Now we should be ready to adjust the constants and then test the mod :)
Or simplified when spikes are considered as cylinders to:
<b>
Damage=KineticenergyToDamage*m/2*(SpikeInitialSpeed+Lerkspeed)^2*e^-(AirDensity*Cd*Distance/(SpikeDensity*CylinderSpikeLength))</b>
That should do it. As for solving the integral, you want to determine the velocity after a certan distance. Tou know the starting velociry and the force acring on the object. But the force depends on the velocity. The result is the dif eq. that Yuuki solved :)
Did a stupid silly misstake, should be correct now.
Constants
rho=1.2047 kg/m^3
Talon density=1390 kg/m^3
For mass I'm going to look up horn density , as spikes are probably similar to that. Dividing away mass and V0^2 with KtD, and make it so that d=0 and x=0 give 1 in result. Looking at one plot for varying speed, and another for varying distance.
With
Spike radius = 0.5 cm
Spike area = 0.7854 cm^2
Spike mean length = 4 cm
spike mass = 4.367 g
Cd = 1
Spikespeed = 52 m/s
Damage
Backward 13 range 20: 36.47%
Backward 13 range 0: 56.25%
Speed 0 range 20: 64.83%
Speed 0 range 0: 100%
Speed 13 range 20: 101.30%
Speed 13 range 0: 156.25%
Speed 20 range 20: 124.30%
speed 20 range 0: 191.72%
Great. For some reason most of the parenthesis and other operators is removed...
Go to <a href="http://www.wolframalpha.com/" target="_blank">http://www.wolframalpha.com/</a> and add this line:
plot (42+x)^2*e^-(1.2047*y/(0.04*1390))/42^2 , -13<x<20, 0<y<20
You should get a 3d plot for y showing distance and x showing speed. 42=SpikeSpeed, 1.0247=AirDensity, 0.04=SpikeLength and 1390=SpikeDensity (taken to be the same material as talons, made of Beta-Keratin).
(I mean, we could try again by using the idea of terminal velocity which does taper off asymptotically, but... I don't think that's really valid, because that's based on a balance between drag and buoyancy forces with gravitational force (opposing forces). In the case of a free-flying lerk spike, there aren't any opposing (to each other) forces, only one force: the drag force.)<!--QuoteEnd--></div><!--QuoteEEnd-->
Yea, terminal velocity wouldn't really apply. So what do you think, should we use the physical approach or the asymptotical one?
<b>Physical version with drag to decrease distance damage</b>
Damage at 0 range
<a href="http://www.wolframalpha.com/input/?i=plot+20%2F47^2*%2847%2Bx%29^2*e^%28-12047*0%2F%281390*0.04%29%29+%2C+-13%3Cx%3C20" target="_blank">http://www.wolframalpha.com/input/?i=plot+...2C+-13%3Cx%3C20</a>
Damage decrease with distance:
<a href="http://www.wolframalpha.com/input/?i=plot+e^%28-1.2047*x%2F%281390*0.04%29%29+%2C+0%3Cx%3C16" target="_blank">http://www.wolframalpha.com/input/?i=plot+...+%2C+0%3Cx%3C16</a>
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->local distToTarget = (trace.endPoint - startPoint):GetLength()
// Have damage increase to reward close combat
local damageDistScalar = Clamp(1 - (distToTarget / kSpikeMinDamageRange), 0, 1)
local damage = kSpikeMinDamage + damageDistScalar * (kSpikeMaxDamage - kSpikeMinDamage)
//Yuuki: Make damage increase based on your speed
//SpikeInitialSpeed=47
//KineticenergyToDamage=40/(m*SpikeInitialSpeed^2)
//Lerkspeed=player:GetVelocity():DotProduct(player:GetViewAngles():GetCoords().zAxis)
//AirDensity=1.2047
//DragCoefficient=1
//SpikeDensity=1390
//CylinderSpikeLength=0.04
//Damage=KineticenergyToDamage*m/2*(SpikeInitialSpeed+Lerkspeed)^2*math.exp(-(AirDensity*DragCoefficient*distToTarget/(SpikeDensity*CylinderSpikeLength)))
local movement = player:GetVelocity():DotProduct(player:GetViewAngles():GetCoords().zAxis)
damage = 20/47^2*(47+movement)^2*math.exp(-1.2047*distToTarget/(1390*0.04))<!--c2--></div><!--ec2-->
<b>Semi-physical approach to drag</b>
Damage from 0 to 2 range:
<a href="http://www.wolframalpha.com/input/?i=plot+20*%28%281%2F%28abs%28x%29%2F35+%2B+1%29%29*x%2F35+%2B+1%29^2++%2C+-13%3Cx%3C20" target="_blank">http://www.wolframalpha.com/input/?i=plot+...2C+-13%3Cx%3C20</a>
Damage decrease from 2 to 16 range:
<a href="http://www.wolframalpha.com/input/?i=plot+%2815%2B5*%281-%28x-2%29%2F%2816-2%29%29%29%2F20++%2C+2%3Cx%3C16" target="_blank">http://www.wolframalpha.com/input/?i=plot+...+%2C+2%3Cx%3C16</a>
For some reason it didn't work to link the exact code. So I used the exactely same shape but with a few unnecessary things.
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->local damageDistScalar = Clamp(1 - ((distToTarget - kSpikeMaxDamageRange) / (kSpikeMinDamageRange - kSpikeMaxDamageRange)), 0, 1)
local damage = kSpikeMinDamage + damageDistScalar * (kSpikeMaxDamage - kSpikeMinDamage)
//Yuuki: Make damage increase based on your speed
//max damage = 20
//min damage = 15
local static = 15 + damageDistScalar * 5
local movement = player:GetVelocity():DotProduct(player:GetViewAngles():GetCoords().zAxis)
//spikespeed = 35
local dragfactor = 1/(math.abs(movement)/35 + 1)
damage = static * (dragfactor*movement/35 + 1)^2<!--c2--></div><!--ec2-->
<b>Sigmoid shaped</b>
Damage at 0 range:
<a href="http://www.wolframalpha.com/input/?i=plot+2*20%2F%281%2Be^%28-%28x-1%29%2F5%29%29++%2C+-13%3Cx%3C20" target="_blank">http://www.wolframalpha.com/input/?i=plot+...2C+-13%3Cx%3C20</a>
Damage decrease at range:
<a href="http://www.wolframalpha.com/input/?i=plot+2*%282%2F5*e^%28-%28x^2%29%2F%28200%29%29%2B0.6%29%2F%281%2Be^%28-0%2F6%29%29+%2C+0%3Cx%3C20" target="_blank">http://www.wolframalpha.com/input/?i=plot+...+%2C+0%3Cx%3C20</a>
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->local distToTarget = (trace.endPoint - startPoint):GetLength()
// Have damage increase to reward close combat
local damageDistScalar = Clamp(1 - ((distToTarget - kSpikeMaxDamageRange) / (kSpikeMinDamageRange - kSpikeMaxDamageRange)), 0, 1)
local damage = kSpikeMinDamage + damageDistScalar * (kSpikeMaxDamage - kSpikeMinDamage)
//Yuuki: Make damage increase based on your speed
//max damage = 18
local movement = player:GetVelocity():DotProduct(player:GetViewAngles():GetCoords().zAxis)
damage = 40/(1+math.exp(-(movement-1)/5))*(0.4*math.exp(-(distToTarget^2)/200)+0.6)<!--c2--></div><!--ec2-->
On eggs, I tried firing of secondary when close at high speed. They have 250 health and no armor, and took 54% damage in one secondary, that's about 135 damage (compare to 120 up close in live). If not moving but still close, they dealt about 31% (78) damage.
For MAC:s, I could often get one kill every two swoops at crevice. This is starting from extreme range and going at max speed at them. Shooting secondary at full speed I managed to reach 27% once, just before I collided with the MAC. They have 325 health and 50 armor.
>> v = u*e^(-kd)
where k = 1/2*rho*Cd*A
where u = lerkspeed + spikespeed = initial velocity
where v = velocity at distance = d
>> KE = m/2*v^2 = m/2*u^2*e^(-2kd)
>> damage = a*KE = a*m/2*u^2*e^(-2kd)
>> damage = b*(lerkspeed+spikespeed)^2*e^(-2kd) ...(1)
But when d = constant
>> damage = b*(lerkspeed+spikespeed)^2*c
So it's still a quadratic, we've just once again changed how damage changes with distance.
btw, for our purposes we can re-write (1) as
>> damage = basedamage/(spikespeed^2)*(lerkspeed+spikespeed)^2*e^(-K*d)
K we need to specify on our own for our purposes, for example,
with no movement (static), that reduces the equation to
>> damage = basedamage*e^(-K*d)
At zero distance,
>> damage = basedamage*e^(0)
so unlike before, our basedamage must be our maximum static damage
i.e.
>> damage = kSpikeMaxDamage*e^(-K*d)
Now we simply have to specify some target.
For example
Let's do kSpikeMinDamage when the distance is kSpikeMinDamageRange:
then
>> kSpikeMinDamage = kSpikeMaxDamage*e^(-K*kSpikeMinDamageRange)
>> ln(kSpikeMinDamage/kSpikeMaxDamage) = -K*kSpikeMinDamageRange
>> K = -ln(kSpikeMinDamage/kSpikeMaxDamage)/kSpikeMinDamageRange
For example, kSpikeMinDamage = 15, kSpikeMaxDamage = 20, kSpikeMinDamageRange = 16
>> K = -ln(15/20)/16 = 0.01798
Ideally, we would do this calculation outside of the lerk or spike loop for efficiency, so perhaps in balance.lua, or whatever gets called least. (Or we could simply evaluate it externally, then hard-code it in, and add a comment.)
But of course if we go further back than that range, we reduce our damage, and we don't want to do that.
So we can add a clamp to set an upper limit, e.g.
>> Clamp(distToTarget, 0, kSpikeMinDamageRange)
But I want to specify a maximum damage range as well
so:
>> Clamp(distToTarget, kSpikeMaxDamageRange, kSpikeMinDamageRange)
but now the problem is that at, say, 0 distance, the "distance" actually reads as kSpikeMaxDamageRange = 2
>> damage = kSpikeMaxDamage*e^(-K*2)
>> damage < kSpikeMaxDamage
so we no longer actually do maximum damage as we wish, so instead, we subtract kSpikeMaxDamageRange from the evaluation and the upper and lower limits:
>> Clamp(distToTarget-kSpikeMaxDamageRange, kSpikeMaxDamageRange-kSpikeMaxDamageRange, kSpikeMinDamageRange-kSpikeMaxDamageRange)
or simply:
>> Clamp(distToTarget - kSpikeMaxDamageRange, 0, kSpikeMinDamageRange - kSpikeMaxDamageRange)
Now
>> damage(d=2) = kSpikeMaxDamage*e^(-K*(2-2)) = kSpikeMaxDamage
>> damage(d=1) = kSpikeMaxDamage*e^(-K*("0")) = kSpikeMaxDamage
>> damage(d=16) = kSpikeMaxDamage*^(-K*(14)) > kSpikeMinDamage
of course, now we realise that now we need to change K as well:
>> K = -ln(kSpikeMinDamage/kSpikeMaxDamage)/(kSpikeMinDamageRange-kSpikeMaxDamageRange)
e.g. kSpikeMinDamage = 15, kSpikeMaxDamage = 20, kSpikeMinDamageRange = 16, kSpikeMaxDamageRange = 2
>> K = -ln(15/20)/(16-2) = 0.020548719
Now
>> damage(d=1) = kSpikeMaxDamage*(e^(-K*("0")) = kSpikeMaxDamage
>> damage(d=17) = kSpikeMaxDamage*(e^(-K*("14")) = kSpikeMinDamage
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->// local distToTarget = ~
// local movement = ~
local spikeSpeed = 52
// kSpikeMinDamage = 15, kSpikeMaxDamage = 20, kSpikeMinDamageRange = 16, kSpikeMaxDamageRange = 2
local distConstant = -math.ln(kSpikeMinDamage / kSpikeMaxDamage) / (kSpikeMinDamageRange - kSpikeMaxDamageRange)
// check natural logarithm function
local distLimited = Clamp(distToTarget - kSpikeMaxDamageRange, 0, kSpikeMinDamageRange - kSpikeMaxDamageRange)
// damage = kSpikeMaxDamage/(spikeSpeed^2)*(movement+Spikespeed)^2*math.exp^(-distConstant*distLimited))
// simplified to:
damage = kSpikeMaxDamage*(movement/spikeSpeed+1)^2*math.exp^(-distConstant*distLimited))<!--c2--></div><!--ec2-->
Keep in mind, however, that this is still a quadratic.
Graph:
<img src="http://i.imgur.com/mQcu0.png" border="0" class="linked-image" />
Constants:
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->kSpikeMaxDamage 20
kSpikeMinDamage 15
kSpikeMaxDamageRange 2
kSpikeMinDamageRange 16
spikeSpeed 52
distConstant 0.020548719 // (calculated)<!--c2--></div><!--ec2-->
<a href="http://www.mediafire.com/?rra70jekgx0v9bf" target="_blank">Excel spreadsheet</a>
Looking at the graph like that, it's practically linear...
<!--quoteo(post=1893108:date=Jan 8 2012, 01:46 PM:name=Fluid Core)--><div class='quotetop'>QUOTE (Fluid Core @ Jan 8 2012, 01:46 PM) <a href="index.php?act=findpost&pid=1893108"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->I did some damage tests against drifters, eggs and MAC:s in summit with the non-physical approach. For drifters (100 health and no armor), I could kill two in one swoop in crevice and have time to avoid contact. Standing on a drifter and shooting a spike, they took 19 damage. Standing on very long range, they took 12 when I hit after 4 spikes or so. When I glided fast against a drifter and shot a spike when close, it did 35 damage. On starting from the ceiling and diving, I managed to reach 37.
On eggs, I tried firing of secondary when close at high speed. They have 250 health and no armor, and took 54% damage in one secondary, that's about 135 damage (compare to 120 up close in live). If not moving but still close, they dealt about 31% (78) damage.
For MAC:s, I could often get one kill every two swoops at crevice. This is starting from extreme range and going at max speed at them. Shooting secondary at full speed I managed to reach 27% once, just before I collided with the MAC. They have 325 health and 50 armor.<!--QuoteEnd--></div><!--QuoteEEnd-->
...wow.
Makes me think we may need to just clamp damage...
Alternatively... we could play around with trying to give the lerk a terminal velocity...
EDIT: Actually, the base damage just seems too high. I mean, in one second, the lerk can deal 12.5 * 20 while at point-blank and standing still. 250 damage per second... seriously?
Let's see... the rifle does 180 damage per second, but you're more likely to miss. I'm thinking we just need to drop the max/min damage range of the lerk spike (so 10~15 for instance, or even less); OR reduce the rate of fire of primary and reduce the number of spikes for secondary, but that way is much less flexible and more punishing.
Changed the constants some, and multiplied the damage by 12.5 to get damage per second:
<img src="http://i.imgur.com/KCjGW.png" border="0" class="linked-image" />
Check my post two above yours, the one with three blocks of code and six links. The links go to the damage graphs and the range scaling.
The models shown are the completely physical approach, the approach you said you prefered, and the sigmoid shape I liled.
<b>Live Damage Comparsion</b>
Up close: 24 damage each spike=300 dps
From range: 20 damage each spike=250 dps
<b>Sigmoid Damage Comparsion</b>
Up close static: 18 damage each spike=225 dps
From range static: 10.8 damage (11?) each spike=135 (137.5) dps
That is assuming perfect hit ratio. Up close all should hit, while for range, they got a eight degree cone insteaad of six like us, so they would miss more. Your circle-rectangle overlap spreadsheet would be gold here. But I saw your point and actually decreased the sigmoid shaped damage to 18 static, while adjusting the slope slightly, and still reach about 36 at 13 speed and about 39 at 20 speed. I think that's warranted considering the short time frame during a swoop, and that the static is reduced to 2/3 of the live one.
Oh, and the formula you got seems to be about the same as what I got, but I inserted the actual constants, so I can't see right of the bat.
The completely physical approach with drag.
<a href="http://dl.dropbox.com/u/49503284/lerkSpikes/lua/Weapons/Alien/SpikesPhysical.lua" target="_blank">http://dl.dropbox.com/u/49503284/lerkSpike...kesPhysical.lua</a>
The semi physical approach with drag for speed damage decrease, but linear damage decrease for range.
<a href="http://dl.dropbox.com/u/49503284/lerkSpikes/lua/Weapons/Alien/SpikesSemiPhysical.lua" target="_blank">http://dl.dropbox.com/u/49503284/lerkSpike...emiPhysical.lua</a>
The mathematical approach with a sigmoid shape. The one I tested against eggs/drifters/MAC:s
<a href="http://dl.dropbox.com/u/49503284/lerkSpikes/lua/Weapons/Alien/SpikesSigmoid.lua" target="_blank">http://dl.dropbox.com/u/49503284/lerkSpike...ikesSigmoid.lua</a>
Can you give a quick summary of what mechanics made it into your final version and try to describe the impact it will have on lerkplay?
The only real mechanic change is to increade your spike damage with forward speed. Strafing doesn't affect the damage, while moving backward decrease it. We are also looking into how range should decrease the damage of the spikes.
The goal of these changes is to change the focus of lerk gameplay away from getting close to a marine and circle them by the roof. Instead we want the lerks to fly in against a marine and then fall back for a new attack . By this change lerks could do a fair bit of damage on the initial charge, and then finnish it of with low damage spikes or take evasive action and prepare for a new attack. At the same time, we want to make the lerks less effective at sieging from afar, and the combination of lower damage while stationary and damage decrease with rsnge, they should want to get close or atleast fly in and out quickly to take down structures.
All this is meant to get the lerk effectively use the ability to fly in combat. To change them from gettinh into your fsce, and staying there, to fly in and out and be the mobile class they should be.
I think this summarize our intents and idea. If i forgot something or made a misstake, please point it out so that I can correct it.
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->kSpikeMaxDamage 20
kSpikeMinDamage 15
kSpikeMaxDamageRange 2
kSpikeMinDamageRange 16
spikeSpeed 52
distConstant 0.020548719 // (calculated)<!--c2--></div><!--ec2-->
and then get the required damage such that:
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->damage = kSpikeMaxDamage*(movement/spikeSpeed+1)^2*math.exp^(-distConstant*distLimited))<!--c2--></div><!--ec2-->
where
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->distConstant = -math.ln(kSpikeMinDamage / kSpikeMaxDamage) / (kSpikeMinDamageRange - kSpikeMaxDamageRange)
local distLimited = Clamp(distToTarget - kSpikeMaxDamageRange, 0, kSpikeMinDamageRange - kSpikeMaxDamageRange)<!--c2--></div><!--ec2-->
1) when movement = 0, distToTarget <= kSpikeMaxDamageRange, damage = kSpikeMaxDamage
2) when movement > 0, distToTarget <= kSpikeMaxDamageRange, damage > kSpikeMaxDamage *
3) when movement < 0, distToTarget <= kSpikeMaxDamageRange, damage < kSpikeMaxDamage *
4) when movement = 0, kSpikeMaxDamageRange < distToTarget < kSpikeMinDamageRange, kSpikeMaxDamage > damage > kSpikeMaxDamage
5) when movement != 0, kSpikeMaxDamageRange < distToTarget < kSpikeMinDamageRange, damage = f(movement, distToTarget)
6) when movement = 0, distToTarget >= kSpikeMinDamageRange, damage = kSpikeMinDamage
7) when movement > 0, distToTarget >= kSpikeMinDamageRange, damage > kSpikeMinDamage *
8) when movement < 0, distToTarget >= kSpikeMinDamageRange, damage < kSpikeMinDamage *
* the lower the spikeSpeed, the greater the degree of difference from the "base" values that increased or decreased movement induces.
Even though this is technically the physical approach, it makes balancing the numbers a very simple affair, since we know exactly what the numbers will come out to be. The work's been done in the excel spreadsheet, you just need to plug in the numbers.
The disadvantage with the physical approach is that when you have incredibly high movement (which you obtain when falling), you get incredibly high damage per spike. Although this could be realistic, it isn't so conducive to game balance. However, this damage is mitigated by at least two other factors: Less time to execute, and higher chance of missing. (I guess I'll get onto that hit-rate spreadsheet today.)
However, <u>if we need to</u> (the two factors above are not sufficient), a way to "hack" a solution to this is to do something like:
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->local movement = ~
length(movement) = length(movement)^0.9
// "length" since movement can be positive or negative, and we only want to change the size of the movement,
// while trying to raise a negative number to a fractional power would be problematic.
// if length() doesn't work, can possibly be done using
movement = sign(movement)*abs(movement)^0.9
// assuming there's a math.sign function (returns -1 if -ve, 0 if zero, 1 if +ve)
damage = ~<!--c2--></div><!--ec2-->
(It doesn't have to be 0.9, it can be less or more, but less than 1 of course.)
Basically, the size of movement is reduced and reduced proportionally to its own size, so increased movement does not lead to such an increased damage.
Illustration:
<img src="http://i.imgur.com/b9I32.png" border="0" class="linked-image" />
<u>A note on the sigmoid function</u>:
I find that the sigmoid function is incredibly difficult to understand, and would require a whole lot of trial and error to change the hard-coded numbers and get good values.
Do you think you could re-write it using soft-coded constants (like what I have above)?
The goals would be the points 1) to 8) that I've described above (minimum static damage at minimum range, maximum static damage at maximum range, etc.; write equations in terms of spikespeed, and maximum and minimum damage and range).
<!--quoteo(post=1893158:date=Jan 9 2012, 03:25 AM:name=Fluid Core)--><div class='quotetop'>QUOTE (Fluid Core @ Jan 9 2012, 03:25 AM) <a href="index.php?act=findpost&pid=1893158"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->I would agree.
The only real mechanic change is to increade your spike damage with forward speed. Strafing doesn't affect the damage, while moving backward decrease it. We are also looking into how range should decrease the damage of the spikes.
The goal of these changes is to change the focus of lerk gameplay away from getting close to a marine and circle them by the roof. Instead we want the lerks to fly in against a marine and then fall back for a new attack . By this change lerks could do a fair bit of damage on the initial charge, and then finnish it of with low damage spikes or take evasive action and prepare for a new attack. At the same time, we want to make the lerks less effective at sieging from afar, and the combination of lower damage while stationary and damage decrease with rsnge, they should want to get close or atleast fly in and out quickly to take down structures.
All this is meant to get the lerk effectively use the ability to fly in combat. To change them from gettinh into your fsce, and staying there, to fly in and out and be the mobile class they should be.
I think this summarize our intents and idea. If i forgot something or made a misstake, please point it out so that I can correct it.<!--QuoteEnd--></div><!--QuoteEEnd-->
Yep, basically this.
Another way to look at it is simply, damage increases with forward speed. <!--coloro:gray--><span style="color:gray"><!--/coloro--><!--sizeo:1--><span style="font-size:8pt;line-height:100%"><!--/sizeo-->based on the concepts of kinetic energy, relative velocities or conservation of momentum<!--sizec--></span><!--/sizec--><!--colorc--></span><!--/colorc-->
We also want damage to decrease with distance. <!--coloro:gray--><span style="color:gray"><!--/coloro--><!--sizeo:1--><span style="font-size:8pt;line-height:100%"><!--/sizeo-->based on the concept of drag<!--sizec--></span><!--/sizec--><!--colorc--></span><!--/colorc-->
The lerk will want to <b>move</b>, play aggressively, and swoop down on targets. The lerk will be Death From Above.
Other implications that Fluid Core hasn't yet definitively described:
- Lerks that fly backwards while shooting will be penalised (do a bit less damage than normal) for attacking while retreating, making it fairer for marines (who are also penalised for the same by having reduced back-walk speed).
- Circling (strafe-flight) or hovering around a target, or sitting still and firing, will not have any damage bonus.
- An implication for the marines is that, because swooping is significantly more effective (you can gather a lot of speed and therefore damage), marines will want to "watch the skies" a bit more when they know lerks are in play and there are high ceilings. A further implication would be that this splits their attention away from the walls and ground where skulks may be, making skulks a little more effective. Or the reverse, skulks in play draws attention to the wall and ground, leaving the lerk open to safely rain death from above.
and that's it, I think.
Oh, and Fluid Core: I just thought of a third factor that would affect the damage when swooping: seen from above, a marine's hitbox is significantly smaller than when seen from the side, at the same distance - more difficult to aim AND even with perfect aim, a lower hit-rate. I'm just a little worried about the secondary (striking at the last second). It might make sense to reduce the damage for secondary (from a realism perspective, there's less force available per spike per shot).
Missclick ate the first half of my post about the function. Stupid phone.
Here we go again...
I hate the side pannel toogle now.
@Harimau:
The approach that I call sigmoid shaped actually consists of two functions; the damage from speed shaped from the sigmoid, while the damage decrease from distance is based on the shape of the normal-distribution. Let's first look at the sigmoid function and I'll show how I reached all the values, while after that looking at the range function.
The basic sigmoid function appear such as:
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->1/(1+math.exp(-x))<!--c2--></div><!--ec2-->
Before we start with the values, you need to understand how it behaves. When x approaches -infinite the value of the sigmoid function approaches 0, when x is 0 the value of the function is 1/2, and when x approaches +infinite the function approaches 1. Now we are ready to start.
1) By multiplying the function with a positive value, we change the magnitude at each point.
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->Damage=2*StaticDamage*/(1+math.exp(-LerkSpeed))<!--c2--></div><!--ec2-->
My initial approach was to regard this multiplier at 2*StaticDamage*, but it is more clear to view it as MaximumDamagePotential for the final function. I will explain why at the third stage.
2) By dividing the variable we reduce the slope of the curve. This was done so that we wouldn't reach withing maybe 1 of the MaximumDamagePotential within speeds as low as 4.
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->Damage=MaximumDamagePotential/(1+math.exp(-Lerkspeed/DamageIncreaseSpeed))<!--c2--></div><!--ec2-->
The only purpose of that value is to change how fast we reach a certain damage.
3) By adding a constant value in the exponent, we offset the entire function left or (like here) right, so that the damage get lower at the same speed.
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->Damage=MaximumDamagePotential/(1+math.exp(-(Lerkspeed+StaticDamageAdjustment)/DamageIncreaseSpeed))<!--c2--></div><!--ec2-->
As you pointed out, the static damage was quite high, and I wanted to reduce it. I was happy with the MaximumDamagePotential however. There are multiple approaches to change the static damage. Since I was happy with the maximum and not to concerned with low damage while moving backward, I choose the approach that I felt would be most computer optimized. The value here may seem to be chosen arbitrary. It is not. I choose the static damage I wanted (18) and found out where the function had that value.
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->18=40/(1+math.exp(-(Lerkspeed)/5))
1+math.exp(-(Lerkspeed)/5)=40/18
math.exp(-(Lerkspeed)/5)=11/9 -- OMG 9/11 Europe style!!
-Lerkspeed/5=ln(11/9)
Lerkspeed=-5ln(11/9)<!--c2--></div><!--ec2-->
By a happy coincidence -5ln(11/9)=-1.003353, and I saw no reason to use up extra computational resources to keep it that exact. So I set it to -1 instead. With that the damage as a function of speed is done.
Now we move on to the High-Low function. In basic form it looks like:
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->math.exp(-x^2)<!--c2--></div><!--ec2-->
We will just look at positive values of x, since the range will always be positive. This function starts of from 1 at zero range, and then starts of slowly decreasing, the slope getting steadily steeper before it starts to pan out towards zero at larger values.
1) By dividing the distToTarget with a positive value (>1) we make the curve less steep.
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->math.exp(-(distToTarget^2)/200)<!--c2--></div><!--ec2-->
The first thing we notice is that this happens very quickly. To reduce this speed we again divide in the exponent. I choose to divide after the speed had been squared, since I think the computer has easier to handle divisions then squaring very small numbers (as we can get at low distances). This value changes how fast we get close to the minimum damage.
2) By adding a constant after the function, we set a cap to how low the function can get.
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->(math.exp(-(distToTarget^2)/(DamageDecreaseSpeed))+MaxRangeMultiplier)<!--c2--></div><!--ec2-->
I felt that a 40% reduction at max range would be about right..
3) By multiplying the exponent part we normalize the relationship.
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1--> ((1-MaxRangeMultiplier)*math.exp(-(distToTarget^2)/(DamageDecreaseSpeed))+MaxRangeMultiplier)<!--c2--></div><!--ec2-->
Since we want the damage to be 100% at zero range, we compensate for the MaxRangeMultiplier by the exponent.
<b>Full function</b>
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->Damage=MaximumDamagePotential/(1+math.exp(-(Lerkspeed+StaticDamageAdjustment)/DamageIncreaseSpeed))* ((1-MaxRangeMultiplier)*math.exp(-(distToTarget^2)/DamageDecreaseSpeed)+MaxRangeMultiplier)
Where
MaximumDamagePotential=40
StaticDamageAdjustment=-1
DamageIncreaseSpeed=5
MaxRangeMultiplier=0.6
DamageDecreaseSpeed=200
Gives
Damage=40/(1+math.exp(-(Lerkspeed-1)/5))*(0.4*math.exp(-(distToTarget^2)/200)+0.6)<!--c2--></div><!--ec2-->
<ol type='1'><li>when movement = 0, distToTarget ~= 0, damage = 1/2MaximumDamagePotential*</li><li>when movement > 0, distToTarget ~= 0, damage > 1/2MaximumDamagePotential*</li><li>when movement < 0, distToTarget ~= 0, damage < 1/2MaximumDamagePotential*</li><li>when movement = 0, distToTarget > 0, damage = f(distToTarget) < 1/2MaximumDamagePotential*</li><li>when movement > 0, distToTarget > 0, damage = 0.6*1/2MaximumDamagePotential < f(distToTarget,movement) < MaximumDamagePotential</li><li>when movement < 0, distToTarget > 0, damage = f(distToTarget,movement) < 1/2MaximumDamagePotential*</li></ol>
<i>*Actual Damage depends on the StaticDamageAdjustment. When it is ~=-1 then 1/2MaximumDamagePotential*~=18</i>
@Insane Secondary damage: I don't think that it's viable to balance the secondary for the extreme case of high ceiling and firing right up close in a dive. That will make the secondary useless in all other situations.
It actually comes out really nicely. There are three "break points" (when the hit-rate takes a sudden decline), the first one is extremely visible, the second is less visible, and the third one isn't really visible at all because the region between the second and third break points is always so small. Here's a preview:
<img src="http://i.imgur.com/Zzd11.png" border="0" class="linked-image" />
I've used hitbox height = 1.8, width = 0.5, firing cone angle (from centre) = 6 degrees, over a range of 0~30 with increments of 0.1.
Also, by duplicating sheets, changing the cone and merging graphs:
<img src="http://i.imgur.com/1M8Gd.png" border="0" class="linked-image" />
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->local kSpread = Math.Radians(6)
// Calculate spread for each shot, in case they differ
local randomAngle = NetworkRandom() * math.pi * 2
local randomRadius = NetworkRandom() * NetworkRandom() * math.tan(kSpread)
local spreadDirection = (viewCoords.xAxis * math.cos(randomAngle) + viewCoords.yAxis * math.sin(randomAngle))<!--c2--></div><!--ec2-->
You need to take account of the random distribution though. Most likely 0<NetworkRandom()<1 and is a linear distribution. spreadDirection can probably be ignored with enough iterations and just rotate the linear spread over the disk.
Like the plot though :)
I've only really roughly formatted the spreadsheet, but here it is:
<a href="http://www.mediafire.com/?4olkffvl27p6ldb" target="_blank">Mediafire</a>
You can pretty much delete all but the first sheet, but the others have some interesting stuff on them.
This is how I brainstormed and planned it:
<img src="http://i.imgur.com/G4Nae.png" border="0" class="linked-image" />
This picture also illustrates why the hit-rate has "break points".
Some assumptions:
- Rectangular hitbox
- Aiming at the centre of the hitbox (optimal condition)
- Height of the hitbox is greater than its width (not really important... it's all relative, you can make it anything, the math just operates under that assumption)
- Linear (even) distribution of shots around the centre in a circular disc
- other stuff I forget, maybe
Now the next step is to take our various damage models and merge them into the document: multiply the theoretical hit-rate by the raw damage per spike (or damage per second), to get the theoretical average damage per spike (or damage per second).
<a href="http://mathworld.wolfram.com/UniformProductDistribution.html" target="_blank">http://mathworld.wolfram.com/UniformProductDistribution.html</a>
I'm not sure how you should interpret it, the angle is also the maximum spread possible and thus defines a cone, but the probability to have a projectile close to this cone is very low.
<img src="http://img233.imageshack.us/img233/9936/spikespread.jpg" border="0" class="linked-image" />
That's a scatter for 100000 points.
<img src="http://img841.imageshack.us/img841/6523/spikespreadhist.jpg" border="0" class="linked-image" />
And that's the histogram for the squared uniform distribution. Again 100000 points.
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->clear
random=rand(100000,3); %uniform distribution
% kSpread = Math.Radians(6);
% Calculate spread for each shot, in case they differ
values=zeros(100000,2);
values(:,1) = random(:,1) .* pi() * 2; %angle
values(:,2) = random(:,2) .* random(:,3); %radius
hist(values(:,2),10000)
y=values(:,2).*(sin(values(:,1))); %y-coordinates
x=values(:,2).*(cos(values(:,1))); %x-coordinates
scatter(x,y,1)<!--c2--></div><!--ec2-->
Ah, well that's no good at all then - so with the model they've used, shots essentially tend towards the centre? I did not know that. Is there a simple way to interpret that for our purposes? Like an approximate function that describes the distribution as a curve (number of shots vs radius)... projected onto the cone's circle... then integrating to find the volume of the projection over the overlap, divided by the volume of the projection over the circle? Integration in three dimensions? I've never even considered it. Is it even viable to do it analytically, as I've done it in two dimensions?
To illustrate, something like this:
<img src="http://i.imgur.com/QwaE3.png" border="0" class="linked-image" />
<!--quoteo(post=1893192:date=Jan 9 2012, 11:16 AM:name=Fluid Core)--><div class='quotetop'>QUOTE (Fluid Core @ Jan 9 2012, 11:16 AM) <a href="index.php?act=findpost&pid=1893192"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->3) By adding a constant value in the exponent, we offset the entire function left or (like here) right, so that the damage get lower at the same speed.
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->Damage=MaximumDamagePotential/(1+math.exp(-(Lerkspeed+StaticDamageAdjustment)/DamageIncreaseSpeed))<!--c2--></div><!--ec2-->
As you pointed out, the static damage was quite high, and I wanted to reduce it. I was happy with the MaximumDamagePotential however. There are multiple approaches to change the static damage. Since I was happy with the maximum and not to concerned with low damage while moving backward, I choose the approach that I felt would be most computer optimized. The value here may seem to be chosen arbitrary. It is not. I choose the static damage I wanted (18) and found out where the function had that value.
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->18=40/(1+math.exp(-(Lerkspeed)/5))
1+math.exp(-(Lerkspeed)/5)=40/18
math.exp(-(Lerkspeed)/5)=11/9 -- OMG 9/11 Europe style!!
-Lerkspeed/5=ln(11/9)
Lerkspeed=-5ln(11/9)<!--c2--></div><!--ec2-->
By a happy coincidence -5ln(11/9)=-1.003353, and I saw no reason to use up extra computational resources to keep it that exact. So I set it to -1 instead. With that the damage as a function of speed is done.<!--QuoteEnd--></div><!--QuoteEEnd-->
Am I right in thinking that the bottom block should actually have the unknown read StaticDamageAdjustment, rather than Lerkspeed? Since Lerkspeed is a variable.
I read through it and the approach and justifications seem pretty reasonable. It does seem somewhat complex for what it does though; is there a way to benchmark the speed of execution? I guess the other, well I won't really call it an issue as such, is that it doesn't conform to reality, it just follows a certain shape... which is fine, really, since we need to mitigate those edge cases that come with high ceilings, which this approach does well.
Regarding the secondary, in what situations is it actually used though? If we do go with a non-sigmoid approach, I feel that the secondary is too powerful at point blank when dropping from a height.
According to my rather complicated calculations (i.e. maybe wrong) it goes like that. The shots at distance d cover a disc of radius rs = d tan(spreadAngle), the area of the target exposed is approximated as a disc with radius rt.
Then for rt < rs the fraction of spikes that hit on average is given by :
<!--c1--><div class='codetop'>CODE</div><div class='codemain'><!--ec1-->f = rt/rs [1 - ln(rt/rs) ]<!--c2--></div><!--ec2-->
and if rt>rs then f=1 (all the spikes hit).
Here is the plot of the fraction of spikes that hit against the ratio of radii :
<a href="http://www.wolframalpha.com/input/?i=plot%28+x+%281-log%28x%29+%29%29+x+between+0+and+1" target="_blank">http://www.wolframalpha.com/input/?i=plot%...between+0+and+1</a>
Ah, well that's no good at all then - so with the model they've used, shots essentially tend towards the centre? I did not know that. Is there a simple way to interpret that for our purposes? Like an approximate function that describes the distribution as a curve (number of shots vs radius)... projected onto the cone's circle... then integrating to find the volume of the projection over the overlap, divided by the volume of the projection over the circle? Integration in three dimensions? I've never even considered it. Is it even viable to do it analytically, as I've done it in two dimensions?
To illustrate, something like this:
<img src="http://i.imgur.com/QwaE3.png" border="0" class="linked-image" /><!--QuoteEnd--></div><!--QuoteEEnd-->
I should be able to get the 2-d shape of the curve (the histogram from center edge). Unfortunately I can't recall how to do it analytically, and couldn't fint it on the web. I will try to do it numerically instead :)
I'm not sure how to continue from that though... I mean, then we rotate the function around and get the volume, but what do we do with that? It is possible to do 3-dim integrals atleast.
<!--quoteo(post=1893219:date=Jan 9 2012, 02:22 PM:name=Harimau)--><div class='quotetop'>QUOTE (Harimau @ Jan 9 2012, 02:22 PM) <a href="index.php?act=findpost&pid=1893219"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->Am I right in thinking that the bottom block should actually have the unknown read StaticDamageAdjustment, rather than Lerkspeed? Since Lerkspeed is a variable.<!--QuoteEnd--></div><!--QuoteEEnd-->
No, it should be LerkSpeed. What I'm trying to do there is to find out at what speed the damage would be 18 instead of 20, and then substitute that speed into the function to shift the entire thing. Compare it to the function y=x. If I want to get y=1 when x=0, then I change the function to y=(x+1)
<!--quoteo(post=1893219:date=Jan 9 2012, 02:22 PM:name=Harimau)--><div class='quotetop'>QUOTE (Harimau @ Jan 9 2012, 02:22 PM) <a href="index.php?act=findpost&pid=1893219"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->I read through it and the approach and justifications seem pretty reasonable. It does seem somewhat complex for what it does though; is there a way to benchmark the speed of execution? I guess the other, well I won't really call it an issue as such, is that it doesn't conform to reality, it just follows a certain shape... which is fine, really, since we need to mitigate those edge cases that come with high ceilings, which this approach does well.
Regarding the secondary, in what situations is it actually used though? If we do go with a non-sigmoid approach, I feel that the secondary is too powerful at point blank when dropping from a height.<!--QuoteEnd--></div><!--QuoteEEnd-->
Do you mean at what speed a certain damage is reached, or regular attacking speeds of a lerk? From my experience you quickly reach 13 speed, with a few flaps. 13 speed should give about 36 (0 range) damage with the current numbers.
With the plot that you just posted, does that take into account both conditions - the area of the target exposed, as well as the distribution of spikes? What are the axes? I'm also not sure about the approximation of the hitbox as a disc - I'm not sure what dimensions would be analogous to these radii. I used the approximation of a rectangle because historically hitboxes have been rectangular prisms - I'm not too sure what approach NS2 takes.
<!--quoteo(post=1893222:date=Jan 9 2012, 10:03 PM:name=Fluid Core)--><div class='quotetop'>QUOTE (Fluid Core @ Jan 9 2012, 10:03 PM) <a href="index.php?act=findpost&pid=1893222"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->No, it should be LerkSpeed. What I'm trying to do there is to find out at what speed the damage would be 18 instead of 20, and then substitute that speed into the function to shift the entire thing. Compare it to the function y=x. If I want to get y=1 when x=0, then I change the function to y=(x+1)<!--QuoteEnd--></div><!--QuoteEEnd-->
Hmm :/ it's just that you've already stated the shift (StaticDamageAdjustment) before you worked out the lerkspeed. I mean, personally I would have just said damage = 18 when lerkspeed = 0, which makes more sense to me on a fundamental level (static => lerkspeed = 0).
To use your analogy, I would have just written it as
y(0) = 1 = (0 + b); then evaluated for b.
It should lead to the same result, just a difference in our reasoning/methodology I guess.
By speed of execution, I meant the game code - basically we don't wanna bog down the server with complicated math.
I guess, in all aspects, especially balance, it just has to be tested. My current preference is the physical approach I last described, though.
<a href="http://mathworld.wolfram.com/UniformProductDistribution.html" target="_blank">http://mathworld.wolfram.com/UniformProductDistribution.html</a>
I did the integration, and yes it takes into account the distribution and the area (assuming disc target and perfect aim).
The axis are the fraction of spikes that hit the target (on y) and the ratio of radii on x :
<a href="http://www.wolframalpha.com/input/?i=plot%28+x+%281-log%28x%29+%29%29+x+between+0+and+1" target="_blank">http://www.wolframalpha.com/input/?i=plot%...between+0+and+1</a>
Basically the fraction grows faster than linear with the radii ratio, because the spikes are centered.
I don't think it's really useful to go into more details, all this is already an overkill, it would be much more simple to jump in game and to tweak the values :)
<!--quoteo(post=1893233:date=Jan 9 2012, 04:29 PM:name=Yuuki)--><div class='quotetop'>QUOTE (Yuuki @ Jan 9 2012, 04:29 PM) <a href="index.php?act=findpost&pid=1893233"><{POST_SNAPBACK}></a></div><div class='quotemain'><!--quotec-->The distribution is given here (red curve):
<a href="http://mathworld.wolfram.com/UniformProductDistribution.html" target="_blank">http://mathworld.wolfram.com/UniformProductDistribution.html</a>
I did the integration, and yes it takes into account the distribution and the area (assuming disc target and perfect aim).
The axis are the fraction of spikes that hit the target (on y) and the ratio of radii (or area) on x :
<a href="http://www.wolframalpha.com/input/?i=plot%28+x+%281-log%28x%29+%29%29+x+between+0+and+1" target="_blank">http://www.wolframalpha.com/input/?i=plot%...between+0+and+1</a>
I don't think it's really useful to go into more details, all this is already an overkill, it would be much more simple to jump in game and to tweak the values :)<!--QuoteEnd--></div><!--QuoteEEnd-->
SWEET!
Second link is broken, can you link the function instead? How did you manage to find it?! Wish I was better at integrals really... They are crazy useful.
What are we waiting for? Set up a server and let's go!