elseagoat Posted February 27, 2013 Share Posted February 27, 2013 (edited) So, I had this idea for a useful script that would determine the location where an arrow has impacted an actor.My idea is to utilize basic dymanics equations to create a formula which will utilize easily obtained information.The equation of motion in one dimension with constant acceleration (note that if acceleration due to gravity in skyrim has a "cap" in that it drops to zero at some point, or varies at all, I am in deep trouble seeing as integrating is not the easiest thing to do in papyrus.) is defined as: Rt = Ro + Vo * T + 0.5 * A * T^2 So I thought, okay, well we can easily obtain the angles that the player is facing the moment the bow is fired, along with his position and thus roughly the point of origin on the projectile. We can find the velocity in the projectile forms for all arrows and they all appear to be at 3600 by default. Additionally, we can easily find the time the arrow is fired, and as long as it actually hits something that a magic effect can be applied to, we can register an onhit event and record the time the arrow landed. With this information, we should be able to locate an X Y and Z coordinates in space exactly where the arrow struck the enemy, and then use this information to do any number of things including some form of accurate locational damage that actually looks at where the target's body parts are in relation to the arrow sticking out of them rather than relying on where the shooter is looking the moment the arrow strikes.Additionally, this could work without modifying any arrow or ammo forms and be compatible with all mods that introduce new arrows.But, there were a few problems that I am stuggling with.First off, I set up a quest. This quest throws the player into an alias so that I can give him an ability, upon which I attached a script with an OnPlayerBowShot event. Alternatively, you could probably register for an animation event OnInit() that corresponds with an arrow being fired if this event doesn't work with crossbows, but the nice thing about OnPlayerBowShot is that it returns the draw strength of the bow. This is important, because it effects the trajectory.So now that we have that script, we can find the angles, position, and time the arrow was fired at. Then we can report them to a script on the quest I created to store them. Now that those are stored, we need to register the time that the arrow hits the actor to actually calculate the final positions of the arrow, and hopefully get it right.Now looking at the equations, we can establish a motion equation for each direction, X, Y, and Z. The Z direction, being up and down, is the only direction that experiences acceleration to my knowledge, unless there is wind resistance or some crap in Skyrim, which I doubt. So the X and Y equations of motion become: Xpos = Xinit + Vxinit * Time Ypos = Yinit + Vyinit * Time where Xpos and Ypos are the final positions we are looking for, Xinit and Yinit are the initial X and Y positions, the ones we got right when the player fired the bow, and Time is the time elapsed from the moment the arrow was fired and the moment it hit the target. Vxinit and Vyinit are a little more difficult. Those are the components of the initial velocity in the X and Y directions. We can calculate those once we have the Z component, which is easier to calculate.The Z direction is the direction that gravity acts in. As such, the motion equation becomes Zpos = Zinit + Vzinit * Time + 0.5 * Grav * Time^2 This is obviously a little more complex. The new variable here, Grav, is the acceleration due to gravity. ;This one is a bit of a mystery. In the projectile forms, it is listed as 0.35. No units are given. So I assume this is a percentage of the maximum gravity experienced by any object in the game. But what is that value, and what units does it have so I can use it in this equation?So I set about creating an Accelerometer to determine this value. I made a spell which causes an arrow to drop in free fall, with zero initial velocity, and record the distance traveled and the start and end times when it hit the ground. The arrow had 0.35 gravity, whatever that means. I applied these distances and time elapsed along with the information that initial velocity was zero, to the equation: Grav = (2* (Zpos - Zinit)) / (T^2) which is just rearranged from the equation above and dropping the initial velocity term.Almost every time I ran the test, I got a value around 233. Sometimes I got anomalies. But it was fairly consistent. The problem is, experimental error will lend to a number which is inexact, and as such, will effect the accuracy of our determination of the arrow's position in our ultimate goal. Remember, we are trying to figure out where in space we achieved a hit with our arrow without actually looking at the arrow itself, because doing so would make our mod extremely incompatible with other mods.So, we have a rough estimate for the amount of gravity experienced by an arrow in flight. Now we need a little trig to get the velocity components and we should be good to go.Lumping the X and Y directions into one dimension in the direction of the velocity vector component in those two directions yields a two dimensional vector. The XY, Z plane. We can use simple sin and cos to break our vector apart to get the magnitude of the velocity in the Z direction. But first, we must remember that in skyrim, arrows are not shot directly though the crosshairs, without editing the INI, they are given a slight upward angle, as if the crosshair is not zeroed at point blank but rather at 10 meters or so. This means we must first fetch this angle from the INI file using the utility.GetINIFloat() function. Then, we must add this to the player's angle around the X axis at the time of firing the bow. But again, we must remember that this angle is inverted when retrieved with the GetAngleX() function, when the player looks up, it goes negative, and goes positive when the player looks down, contrary to intuition. So we multiply GetAngleX() by negative one and add Utility.GetINIFloat() to retrieve the additional firing angle. Now we take the Sin of this value, and multiply it by our arrow speed, which is 3600 according to the form (we just hope these units actually work out, which they dont).Now, we have a value for Vzo. We need to find Vxo and Vyo. Since we broke our 3600 into a Z component, we are left with an XY component we can use and break it apart further. So we take Cos of the value above, retrieved from the GetAngleX() function and GetINIFloat() function. That yields the magnitude of our velocity in the XY plane, or horizontal plane. We need the direction too, to get the components. I could be wrong, but this is what I think we should do. Take the angle around the Z axis which is the angle that the player is turned, and multiply the magnitude we just got times the SIN of that angle around the Z axis to yield the Y component, and COS to yield the X component.The end result looks like this. Xpos = Xinit + Vxinit * Time Ypos = Yinit + Vyinit * Time Zpos = Zinit + Vzinit * Time + 0.5 * Grav * Time^2 Where: Vxinit = (ArrowVelocity * Cos(Utility.GetINIFloat("f1PArrowTiltUpAngle") + (-1 * Game.GetPlayer().GetAngleX())) * Cos(Game.GetPlayer().GetAngleZ()) Vyinit = (ArrowVelocity * Cos(Utility.GetINIFloat("f1PArrowTiltUpAngle") + (-1 * Game.GetPlayer().GetAngleX())) * Sin(Game.GetPlayer().GetAngleZ()) Vzinit = (ArrowVelocity * Sin(Utility.GetINIFloat("f1PArrowTiltUpAngle") + (-1 * Game.GetPlayer().GetAngleX())) All taken at the time the arrow was fired with ArrowVelocity being the velocity of the arrow taken from the projectile form multiplied by afPower returned from the event OnPlayerBowShot(), additionally: Time = TimeHit - TimeShot Now onto the questions.1) Is my math even remotely correct?2) Is it possible to do this another way, using a script to fire a projectile right when an arrow is fired that mimicks the trajectory of the arrow? Possibly only with a full power bow draw to avoid that problem? I can't seem to get "Cast()" to work on the player to do this, and even then, we would encounter problems with the INI setting mentioned above which makes arrows, but not spells, fire at a slight upwards angle. Perhaps using an activator placed using trig to acquire this angle and firing the projectile from the player towards the activator would work, but would introduce timing problems.3) Does anyone know the exact value in units/(s^2) of the acceleration due to gravity in this game? Edited February 27, 2013 by elseagoat Link to comment Share on other sites More sharing options...
CORaven Posted February 27, 2013 Share Posted February 27, 2013 With regards #1. Your mathematics of motion and such appear to be correct to me. I am not familiar with what the variables are called or referenced by the game, but it seems like it should work. Regarding #2, I cannot be of assistance. #3. I think that running more tests on dropping objects should be conducted. Setting the variable of gravity from 0.35 to 1, and comparing the item falling a distance of x and a distance of 2x may yield a greater understanding on how the game handles gravity. Comparing these times, and then comparing them to items with a variable of 0.35 will advance your knowledge. Link to comment Share on other sites More sharing options...
elseagoat Posted February 28, 2013 Author Share Posted February 28, 2013 Thanks for the input! I wish someone knew the exact value for acceleration in the game due to gravity though. I also hope that there aren't other weird factors that effect the trajectory, I have heard that bow speed effects the range and I hope those people that say that don't know what they are talking about. Link to comment Share on other sites More sharing options...
mercuito Posted February 28, 2013 Share Posted February 28, 2013 (edited) I like what I read so far, seems to have potential. Just need to account for the timescale variable in the conversion of the GetCurrentGameTime() return value and I think any slomo effects that take place are not a problem since it effects the passing of game time. But I want to be sure. Once you have that, getting the initial xyz should be doable, but you would need it for the cases of bows, crossbows and spells. The method for getting the velocity is pretty solid. I'm more than glad to work with you on getting a good projectile motion model. I had to do this for my mod but I used a different means to get impact locations for projectiles. This way seems the most promising though, I just didn't like how you couldn't get the projectile speed dynamically and had to look in forms, but I definitely want to work with you in developing a good projectile prediction model for Skyrim. Edited February 28, 2013 by mercuito Link to comment Share on other sites More sharing options...
Kahmul78 Posted March 9, 2013 Share Posted March 9, 2013 I just saw this thread and thought I should write something in here.First of all, I didn't understand everything you were talking about, lol. But I find it far too complex. Gravity affecting the arrow is so small it isn't really noticable. Aside from that I think you didn't include some important factors like the height difference, the height of the actors itself and so on.I created a new detection system for version 2.2 of my Locational Damage mod and everythings works nearly perfectly (one thing is still subject to change) from any angle, position etc. You find it below, I'll release it anyway when I release v2.2. I'm going to share my thoughts with you that I had while creating this system:First, I'm saving the player's position, X angle and if he's sneaking or not when he shoots. I'm not using OnPlayerBowShot for that though as it doesn't trigger when using a crossbow. Instead, I'm just using the "arrowRelease"-animation event for that and the OnPlayerBowShot to get the power of the shot. Then I save everything in my quest script.Event OnAnimationEvent(ObjectReference Source, string asEventName) if(source == playerRef) if(asEventName == "arrowRelease") script.shotAngle = playerRef.getangleX() script.xPos = playerRef.getpositionX() script.yPos = playerRef.getpositionY() script.zPos = playerRef.getpositionZ() script.shotIsSneaking = playerRef.isSneaking() endif endif EndEvent Event OnPlayerBowShot(Weapon akWeapon, Ammo akAmmo, float afPower, bool abSunGazing) script.shotPower = afPower EndEventWhen the player then hits someone with the arrow I can use these values for the calculation. First I'm going to determine the distance between the shot position and the target's postion:; xyzPos = player position ; xyzPosMe = target position dist = sqrt(pow((xPos - xPosMe), 2.0) + pow((yPos - yPosMe), 2.0) + pow((zPos - zPosMe), 2.0))When no crossbow was used I also use the gravity and the power of the shot:; xA = X angle on shot ; dist = distance given by getdistance() float power = script.shotPower xA /= power dist += (dist*getgamesettingfloat("fArrowGravityMult"))This makes sure the correct hit location is found by the calculation.Now to the actual detection.Intially I calculate the height of the attacker and target taking into account if they're sneaking or not as well as their scale.Next I'm going to detect if the attacker is aiming to the arms/legs and if yes which arm or leg, using somewhat predefined values (this is actually still subject to change because it's from my old system and I cannot save the headingAngle to a target on shot).After that I calculate the "real" distance between the target and the attacker using a triangle consisting of the distance given by the getdistance()-function as the hypotenuse and the height difference as the opposite.Now I can calculate the adjacent by using the Pythagoras' theorem.After that I calculate the height difference between the attacker's head (where his "camera" is set) and different heights of the target representing the different body zones.This helps me getting "angle ranges" for the body zones by using the arctangent.Then I compare these angle ranges with the X angle and check in which range the attacker is and then know which bodyzone was hit.Pretty simple if you think about it. But it works amazingly well.; 1 = head ; 2 = neck ; 3 = nape ; 5 = Rshoulder ; 6 = Lshoulder ; 7 = Rarm ; 8 = LArm ; 9 = chest ; 10 = back ; 11 = groin ; 12 = legs ; 13 = feet ; headingAngle = attacker.getheadingAngle(target) ; facing = target.getheadingAngle(attacker) int function hitDetection(Bool UsedPowerattack) scale = attacker.getscale() scaleMe = me.getscale() height = attacker.getheight()*scale heightMe = me.getheight()*scaleMe if(bIsSneaking) height *= 0.75 endif if(bIsSneakingMe) heightMe *= 0.75 endif rangedScale = 1 + (scaleMe - scale) shoulderDisp = 3.5*rangedScale armDisp = 12.1*rangedScale disposition = dist * sin(headingAngle) float dispHeight = zPos - zPosMe dist = sqrt(pow(dist, 2.0) - pow(dispheight, 2.0)) Float pos = (zPos + height) heightDist = pos - (zPosMe + (heightMe*0.9)) headZone = atan(heightDist/dist) heightDist = pos - (zPosMe + (heightMe*0.85)) neckZone = atan(heightDist/dist) heightDist = pos - (zPosMe + (heightMe*0.75)) shoulderZone = atan(heightDist/dist) heightDist = pos - (zPosMe + (heightMe*0.5)) chestZone = atan(heightDist/dist) heightDist = pos - (zPosMe + (heightMe*0.4)) groinZone = atan(heightDist/dist) heightDist = pos - (zPosMe + (heightMe*0.2)) legZone = atan(heightDist/dist) if(xA < headZone) return 1 elseif(xA < neckZone) if(facing <= -135 || facing >= 135) return 3 else return 2 endif elseif(xA < shoulderZone) return shoulderhitCalc(UsedPowerattack) elseif(xA < chestZone) return armHitcalc() elseif(xA < groinZone) if(abs(facing) < 45) return 11 else return 12 endif elseif(xA < legZone) return 12 else return 13 endif endfunction int function armHitCalc() if(abs(facing) < 45) if(disposition > armDisp) return 7 elseif(disposition < -armDisp) return 8 else return 9 endif elseif(facing < -45 && facing > -135) return 8 elseif(facing > 45 && facing < 135) return 7 elseif(abs(facing) >= 135) if(disposition > armDisp) return 8 elseif(disposition < -armDisp) return 7 else return 10 endif endif endfunction int function shoulderHitCalc(bool UsedPowerattack) if(abs(facing) < 45) if(disposition > shoulderDisp) return 5 elseif(disposition < -shoulderDisp) return 6 elseif(type != 7) if(UsedPowerattack) return 4 else return 9 endif else return 4 endif elseif(facing < -45 && facing > -135) return 6 elseif(facing > 45 && facing < 135) return 5 elseif(abs(facing) >= 135) if(disposition > shoulderDisp) return 6 elseif(disposition < -shoulderDisp) return 5 else return 10 endif endif endfunctionI hope it's readable because the forum seems to add extra blank lines after every command. If you have any questions just ask. :-)Kahmul Link to comment Share on other sites More sharing options...
unuroboros Posted March 10, 2013 Share Posted March 10, 2013 Caveat: Mods, and even industrious INI file tweaking, can completely change the ballistics of arrows and bolts... even gravity itself, for that matter. Link to comment Share on other sites More sharing options...
Kahmul78 Posted March 10, 2013 Share Posted March 10, 2013 I don't think anyone would want a gravity modification so massive that it would completely change the trajectory of arrows and bolts. You can disregard this in my opinion. Link to comment Share on other sites More sharing options...
elseagoat Posted March 12, 2013 Author Share Posted March 12, 2013 (edited) I very, very much appreciate you posting this. Warms my heart when people share their scripts like this and I applaud you. I have glanced over it and haven't had much time to really work on this, but from what I am looking at it looks like there is a shortcoming with your method. Forgive me if I am wrong, and I am by no means trying to slight your excellent script or abilities, merely trying to consider all the possibilities for locational damage detection. First, it appears that your script cannot take into account actor animations. For example, if you hit an enemy in the arm while he was performing an overhead power attack with a two handed weapon, whilst his arm was above his head, it looks like your script may not be able to compensate for this as it uses predefined values to determine hit locations in space assuming a perfectly rigid figure facing the player in a specific idle pose. Am I incorrect in assuming this? I have successfully implemented very accurate hit detection on custom spells. The method I used was to pre-load a few custom empty activators with scripts, and have the magic effect of the spell move them to nodes on the target's body if they exist as fast as possible, as this is time-sensitive. The magic effect projectile uses an explosion which places another activator. This activator runs a script, which finds the nearest "node activator" and then reports which one it found to a quest script. The magic effect then retrieves this information from the quest script and does something (double damage, extra effects, ect.) The result is very accurate locational damage that can detect arm and headshots on the fly in the middle of chaotic fights with fast moving enemies. For melee locational damage, I imagined having a script which simply casts a spell with an invisible fast moving projectile, functioning as any custom spell would with the above method, whenever the player performs some melee animation or with onhit events on nearby actors. I havent actually tried this yet. Bows seemed the hardest to implement. The method in the op is the best I could come up with. Obviously, you could attach explosions to the arrow projectiles and get quick and dirty locational damage that way, but the problems with this method are major, ranging from compatibility issues to arrows being unrecoverable after being shot. Your script looks promising, and similar to my "theory" outlined above in many ways and different in others. Hopefully soon I will get a chance to sit down and grind through some testing. Edited March 12, 2013 by elseagoat Link to comment Share on other sites More sharing options...
Legaldose Posted December 18, 2013 Share Posted December 18, 2013 If anyone still cares (doubt it). I've actually calculated the acceleration in units/s^2 and (slightly less accurately) m/s^2 due to gravity. There's a link to my data spreadsheet that I used at the bottom. Final values are on the left most column, A21 is the value you asked for. If you wanna know how I did it (spoiler: it's exactly the way you would think to do it), watch my video on the subject. Spreadsheet Video Link to comment Share on other sites More sharing options...
Recommended Posts