No announcement yet.

Why does the tank minigun turret damage code contradict experience?

  • Filter
  • Time
  • Show
Clear All
new posts

    Why does the tank minigun turret damage code contradict experience?

    I'm really confused. Here are the default properties for ONSWeapon/ONSHoverTankSecondaryTurret:

    AmbientEffectEmitterClass=class'Onslaught.ONSRVCha inGunFireEffect'



    Notice that DamageMin and DamageMax are both set to 17, and that the FireInterval is 0.1 seconds. That means that in theory the minigun turret should do 17/0.1 = 170 points of damage per second.

    And yet, it takes 36 seconds to take down a core with the miniturret, and 76 seconds to take down a core. This means that the damage should be approximately between 2000/36 = 56 and 4500/76 = 60, or the average of the two: about 57 damage per second.

    170/3 = 57. Is this factor of 3 coincidence? What is going on???

    Going through the other vehicles, they were all consistent with their code. (You need to watch out for extra damage done against nodes by plash weapons such as the tank main gun. This is because both the energy sphere and the base get hit so you get more damage than you expect... cores on the other hand, make better test subjects for splash weapons).

    Someone please explain the discrepancy before I go mad! :cry:

    Hehe, this is actually pretty easy, but not so obvious.

    Heres the solution:


    function TakeDamage(int Damage, Pawn instigatedBy, Vector hitlocation, Vector momentum, class<DamageType> damageType)
    	local Controller InstigatorController;
    	//if (instigatedBy != None && ScriptedController(instigatedBy.Controller) != None)
    	//	Log("SCRIPTED DAMAGE:"@Damage@"CORESTAGE:"@CoreStage@"HEALTH:"@Health@"POWEREDBYINSTIGATED:"@PoweredBy(instigatedBy.GetTeamNum()));
    	if (CoreStage == 4 || CoreStage == 1 || Damage <= 0 || Level.Game.ResetCountdown > 0 || Level.Game.bGameEnded)
    	if (damageType == None || !damageType.default.bDelayedDamage)
    		DelayedDamageInstigatorController = None;
    	if (instigatedBy != None && instigatedBy.Controller != None)
    		InstigatorController = instigatedBy.Controller;
    		InstigatorController = DelayedDamageInstigatorController;
    	if (InstigatorController == None && damageType != None)
        if (damageType != None)
    		Damage *= damageType.default.VehicleDamageScaling;
        if (instigatedBy != None)
        	if (instigatedBy.HasUDamage())
    		Damage *= 2;
    		Damage *= instigatedBy.DamageScaling;
        if ( InstigatorController == None || (InstigatorController.GetTeamNum() != DefenderTeamIndex && PoweredBy(InstigatorController.GetTeamNum())) )
    		NetUpdateTime = Level.TimeSeconds - 1;
        	AccumulatedDamage += Damage;
        	if ((DamageEventThreshold > 0) && (AccumulatedDamage >= DamageEventThreshold))
        		TriggerEvent(TakeDamageEvent,self, InstigatedBy);
        		AccumulatedDamage = 0;
    	if (InstigatorController != None)
    		LastDamagedBy = InstigatorController.PlayerReplicationInfo;
    		LastAttacker = instigatedBy;
    		AddScorer(InstigatorController, FMin(Health, Damage) / DamageCapacity);
    	if ( bFinalCore )
    		NetUpdateTime = Level.TimeSeconds - 1;
        Health -= Damage;
        if ( Health < 0 )
    	else if (damageType != None)
    		//attack notification
    		if (LastAttackMessageTime + 1 < Level.TimeSeconds)
    			if (bFinalCore)
    				if (float(Health) / DamageCapacity > 0.55)
    					BroadcastLocalizedMessage(class'ONSOnslaughtMessage', 7 + DefenderTeamIndex,,, self);
    				else if (float(Health) / DamageCapacity > 0.45)
    					BroadcastLocalizedMessage(class'ONSOnslaughtMessage', 25 + DefenderTeamIndex,,, self);
    					BroadcastLocalizedMessage(class'ONSOnslaughtMessage', 18 + DefenderTeamIndex,,, self);
    				BroadcastLocalizedMessage(class'ONSOnslaughtMessage', 9 + DefenderTeamIndex,,, self);
    			UnrealTeamInfo(Level.GRI.Teams[DefenderTeamIndex]).AI.CriticalObjectiveWarning(self, instigatedBy);
    			LastAttackMessageTime = Level.TimeSeconds;
    		LastAttackTime = Level.TimeSeconds;
        else if (PlayerController(InstigatorController) != None)
        	if (InstigatorController.GetTeamNum() != DefenderTeamIndex)
    	        PlayerController(InstigatorController).ReceiveLocalizedMessage(class'ONSOnslaughtMessage', 5);
    		else if (bFinalCore && DamageType == class'DamTypeLinkShaft')
    			PlayerController(InstigatorController).ReceiveLocalizedMessage(class'ONSOnslaughtMessage', 30);
    If you look in the DamageType for the turret, which is DamTypeONSChainGun:

    The factor of 3 is actually a multiplyer of 0.36, heh. I wouldn't call that a coincidence, just a not-so-obvious solution.

    Its important to remember that each class has the ability to handle its own TakeDamage, so when a weapon hits it, it gets a chance to handle that damage differently than other classes. Power cores apparently get to scale damage down to make them last longer.


      LOL! Yes I suspected that but I didnt actually take time to check the code of PowerCore till now (because I thought that was a little stupid!). I just now found the answer and came back to post here . THank you though!


        Re: Why does the tank minigun turret damage code contradict experience?

        Originally posted by PNutButterSnake
        You need to watch out for extra damage done against nodes by plash weapons such as the tank main gun. This is because both the energy sphere and the base get hit so you get more damage than you expect...
        I suspected this for a while... Would you happen to know how far apart the two parts are?

        I think I'll try to rewrite HurtRadius to only hurt nodes once, not twice...


          I believe they are approximately 200 units apart center to center. It might be closer to 180 or 190 though.

          It would be a pain to try to change all of the inidividual HurtRadius functions (not good programming practice either). Instead, you could change the TakeDamage() function of the energy sphere.

          What you could do is attenuate the damage taken by distance even more strongly than it is now. Right now, projectiles and other damaging actors call their own HurtRadius methods which in turn call TakeDamage for all the actors within their DamageRadius.

          HurtRadius use an Inverse Law with respect to distance between the center of itself and the damaged actor (1 - dist/DamageRadius) to scale the damage (attenuate it with distance). You could leave that as it is. But in the single TakeDamage function of the Actor/ONSPowerNodeEnergySphere class, you could attenuate further by scaling by 1/dist yet again. The function gets the hitlocation as an argument, and could take the difference between its center and hitloc to be dist, and scale the damage argument by (1 - dist/D) where D is some predetermined constant that works well. The attenuation will be far sharper so that splash from the base to the sphere is not much of a problem. However, splash from the sphere to the base will still be a problem , unless you also do the same for the base (bad idea imo because then you will weaken the players' ability to hit the base too much).

          The Paladin, by the way, is UTTERLY confounding me. It does not seem to follow the code at all! It does FAR more damage per second to nodes and cores when using primary fire than it should! I even checked for VehicleDamageScaling and that was not it (i believe it is not even set so the default of 1 is used).


            Looks like it deals 150 per shot, to me.


              - Paladin primary fire (no shield up): It took 15 seconds to kill a node and
              50 seconds to kill a core.

              For nodes: 2000/15 = 133.33 damage per second.
              133.33/4500 * 100% = 3.0% core health per second.

              For cores: 4500/50 = 90 damage per second.
              90/4500 * 100% = 2% core health per second.

              The damage amount is supposedly 150, and the refire time is 2.35 seconds. Therefore 150/2.35 = 63.83 points of damage per second should be possible. But instead, I am seeing more than twice that amount against nodes. Even with the energy sphere splash damage forwarding issue, it should not even be doubling it.

              But look at what it does to cores! It is doing 90 damage per second instead of 63.83!


                Just a note, if you take the time at 2.36 seconds, then your damage is:

                300/2.36 = ~127 dmg/sec

                Taking rough times is not a good way, try the number of shots. Or stop trying to find bugs in code that goes beyond your scope.

                Also see above again on Splash damage and other code making modifiactions.


                  First, I am not trying to find bugs.

                  Second, I am trying to *understand* what I am misssing.

                  Third, if you are alluding to my other post about vehicle entries, I *misread* a || as an &&. That was embarrassingly dyslexic, but does not imply that the code is "beyond" me. I am an electrical engineer by training, and I can read code (although I do occassionally make dyslexic errors like that!).

                  Fourth, the code itself is set to 2.35 (see ONSShockTankProjectile) so that was no approximation.

                  Fifth, would a +0.01 in a denominator really make that much difference in the division? Or is it just because you used *twice* the numerator in that division? Where did you get the number 300 from? That is twice as much as specified.

                  Sixth, my experiments were taken over long periods of times (for whole nodes/cores), AND I repeated them multiple times to make sure. That is no approximation. Btw, I did the experiment on the tank too, and it worked out just as expected (cores were affected just as the settings in the code would imply, with no funny business, while nodes were effected more greatly due to the splash forwarding effect).

                  Seventh, as for splash damage (I was the one who posted that btw), even for nodes, it will NOT double the damage if you work out the geometry, because either the base or the sphere, (although they both receive damge), receives an attenuated amount of damage because the shot does not directly touch it, and only splash does (see the Hurt Radius functions, in particular the version for ONSShockTankCannon which uses a typical scaling factor on damage whose maximum value is one). THe maximum damage to the one that receives the shot directly would be 150, while the other component would receive something less than 150 attenuated by the DamageRadius (btw, the DamageRadius of ONSShockTankProjectile is 250). The tota would be less than double.

                  Eight, as for cores, there is none of this damage forward business that the energy sphere have on nodes, so this splash thing should not even have effect, or at least not using this mechanism.

                  Ninth, I am missing something, just as I was missing VehicleDamageScaling above (but eventually found it by myself, indepdently of the poster). In this case I can *not* seem to find what I am missing here. This is in no way a suggestion that there is a bug! quite the contrary, if I thought there was a bug, I would have probably a good reason to think so and post it here. I have *nothing* which means, bug or no bug, I am *missing something*. I want to know what it is that I am missing. I am not a code troll. I am rather a mapper/coder who needs to understand the code for projects I am doing.


                    The reason I brough up the 300/2.36 sec is because if you fire two shots, but take the time instantly after the 2nd shot, your dmg/sec will be higher than if you took the time just before the 3rd shot.

                    2.35 sec reload (assuming instant fire)

                    150/2.35 = ~64 dmg/sec
                    300/2.36 = ~127 dmg/sec
                    300/4.70 (or 4.69) = ~64 dmg/sec

                    So if you are using strictly time vs dmg to calc it, when you take 15 seconds to nail the node, then you are taking 15/2.35 = ~6.4 shots total?

                    With your times you gave, it would then take (granted there be some error if it waits untill after the fx to process b4 beinging the fire delay timer)

                    7 shots to kill a node - 7*150 = 1050 dmg
                    22 shots to kill a core - 22*150 = 3300 dmg

                    The resulting damage is acounted for by splash damage. As you mentioned, the node gets hit twice, thus the damage and splash damage are near the same, 1050 + 950 = 2000
                    While the core, consisting of 1 part, gets hit only once, but still gets added damage from the splash (as with destroyable objs) 3300 + 1200 = 4500

                    If you want more indepth coverage, then I suggest you try taking down a node/core with a non splash weapon like a shock rifle prm and then do it with rox. Then knowing the damage of those, see if they splash one does more than it claims, which I believe it will.


                      Ah, but that is irrelevant. I would never run experiments that short .

                      The refire time does indeed always come *after* the shot, but I was talking about continuous firing for long periods of time, for which you can approximate by doing just damge/refire_time.

                      Here are the two ways the damage/sec can be found:
                      1) Divide the damage amount in the code by the refire time in the code, assuming we are firing for long periods of time
                      2) Kill a core or a node and see how many seconds it takes. Repeat this multiple times. Absolutely no code is needed for this part.

                      The two agree in most cases, except for the Paladin in my understand. Obviously my understanding is imperfect and I am missing something in the code . It is as though some piece of code somehwere snuck in and upped the Paladin's damage against nodes/cores considerably, and this sounds like VehicleDamageScaling, but there was no such property in the damage type of the paladin's primary fire (so it would use the default from the super class which is set to 1.0).

                      Even with effects and all, the refire is still the amount of time from the shot starting, to the next time a shot can start.

                      I did btw run several tests for many weapons. IIRC, all of them (except for Skymines) worked as expected. The skymines IIRC were still within reason except that some cursory geometry/math predicted an amount of damage done to a node that was less than what they actually do (but they were not more than double or anything weird like that).

                      The tank main gun for instance took 37 seconds against a full core, which was what was expected.

                      I don't think splash would matter with single projectiles. Although, in the game, we tend to think of the tank shell or paladin shock ball as clumps of breakable matter, in the code they are singular objects (what we see is just effects). Those singular objects (projectiles) call HurtRadius, which is defined for the superclass "Projectile". Some subclasses override this function with a more specific version, but they all seem to follow this basic pattern:

                      - find all the actors within the DamageRadius propery
                      - for each of these actors give each a scaled version of the Damage property, where the scaling is something like DamageScale = 1 - (dist/DamageRadius), with dist being the distance between the center of the projectile, and the center of the particular actor.

                      There is nothing in there typically for bouncing off walls or reverberations of any sort that might somehow cause more damage. Even for FlakChunks, they are just composed of multiple independent Projectils. I am pretty sure each of these bounces off world geometry for a while, and then either just die off, or if they "get lucky" find flesh/vehicles/nodes/cores and do damage to them in the exact same way as I just described.

                      The only strange splash effect I know off in ONS at least, is the fact that energy sphere can be one of found actors within the radius, while simultaneously, the base is also one of the found actors within the radius IF the DamageRadius is set high enough (more than about 180~200 units since that is the distance between the energy sphere center and the base).

                      IIRC correctly, the DamageRadius of the Pal is only 200 or so. That means that the scaling should be nearly 0! So there should not be a whole extra 950 points added on for nodes. There should be very very little extra damage beyond 150.

                      I think something else is going on here; that it is not splash damage effects but some kind of sneaky extra scaling adjustment somewhere just for the Pal in particular it seems.


                        Has anyone seen this mysterious "area damage attenuation by distance" in effect? I know what it's supposed to do, but in practice anyone caught in a high damage radius seems to get gibbed no matter how far from "ground zero" they are.

                        Take, for example, the Goliath cannon. If you get caught at the edge, you should be able to survive. But how often have you seen this happen? Not 1 in 3 times...


                          Are you kidding???? I hit people and mantas all the time with the goliath main gun and they go FLYING but not DYING.

                          Remember also the huge amount of turret lag that appears to victums. I have found this lag to actually be quite consistent. The turret is head of where it *appears* to be rotationally by about 45 degrees (??? not sure the exact number). So if it appears that you enemy is not pointing at you very precisely, in reality he probably is .