Jump to content
Sign in to follow this  

Projectiles Explained

Recommended Posts

I spent a decent amount of time looking at the client code to figure out how projectiles work precisely so I know how to manipulate them with the parameters better. I thought I might as well share what I learned to help others. 

This is from Major's refactored 317 client (https://github.com/zg/317-client).
I'd guess that projectiles are handled in most revisions in the same way, but I only looked at 317. 

if (opcode == 117) {
    int positionOffset = buffer.readUByte();
    int sourceX = localX + (positionOffset >> 4 & 7);
    int sourceY = localY + (positionOffset & 7);
    int destinationX = sourceX + buffer.readByte();
    int destinationY = sourceY + buffer.readByte();
    int target = buffer.readShort();
    int graphic = buffer.readUShort();
    int sourceElevationOffset = buffer.readUByte() * 4;
    int destinationElevation = buffer.readUByte() * 4;
    int ticksToEnd = buffer.readUShort();
    int ticksToStart = buffer.readUShort();
    int elevationPitch = buffer.readUByte();
    int leapScale = buffer.readUByte();
    if (sourceX >= 0 && sourceY >= 0 && sourceX < 104 && sourceY < 104 && destinationX >= 0 && destinationY >= 0
            && destinationX < 104 && destinationY < 104 && graphic != 65535) {
        sourceX = sourceX * 128 + 64;
        sourceY = sourceY * 128 + 64;
        destinationX = destinationX * 128 + 64;
        destinationY = destinationY * 128 + 64;
        Projectile projectile = new Projectile(sourceX, sourceY, method42(sourceX, sourceY, plane)
                - sourceElevationOffset, destinationElevation, elevationPitch, ticksToStart + tick, ticksToEnd + tick,
                leapScale, plane, target, graphic);
        projectile.target(destinationX, destinationY, method42(destinationX, destinationY, plane) - destinationElevation,
                ticksToEnd + tick);

For those more interested, the rest of the code referred to is in Projectile.java.

Firstly, the start position of the projectile is sent in packet 85 and stored in localX and localY just before this packet is sent. 

positionOffset (incorrectly called angle sometimes in server) is often always sent as 0. This allows you to offset the projectile from the start and end position by a small amount. The positionOffset is an unsigned byte with the first 4 bits representing the x offset and the last 4 bits representing the y offset, so these should give you a scope of 0-15 squares to vary from the starting position for x and y (requires some simple tweaks to get full range and positive/negative offsets, default 317 just has 0-7 offset for x and y in the right and up direction because of the >> 4 & 7 and & 7). 

The next two bytes sent are for offsets to add onto the starting position (with positionOffset applied) to get the destination position stored as destinationX and destinationY.

target is important (sometimes called lockon) and it takes a signed short. A value of 0 represents no tracking and is used for a position to position projectile. Otherwise it will track an npc or player. To track an npc you send the npc's index + 1 and to track a player you send the player's index as (-1 - index). The +1 and -1 are just to compensate for 0 not being an option since that represents no tracking and entity indexes also have 0 as valid player/npc indexes. 

graphic is just the id of the graphic/spotanim that is the projectile to send/

sourceElevationOffset and destinationElevation are used for the start and end height of the projectile, I didn't look into this much but might edit the post if somebody in the comments has useful information or I look into this myself. I probably will look into this to see what the feasible max and min values are for this parameter. The sourceElevationOffset adds onto the floor height that is at the start position and destinationElevation adds on to the floor height at the end position as you'd expect. 

ticksToStart and ticksToEnd are how you control the duration and delay of the projectile. They are measured in client frames and the client should run at 50 fps so this is 20ms or 0.02 seconds. The ticksToStart value represents the number of client frames to wait before starting the projectile. The duration of the projectile is ticksToEnd - ticksToStart. So sending ticksToStart as 30 would be a 1 tick delay. This would be much better to use rather than tasks or delays server sided before sending projectiles which quite a few poorly written servers do. Server sided it would probably be a good idea to change the parameters to specify the ticksToStart as delay and make a duration parameter. Then send the client delay for ticksToStart and delay + duration for ticksToEnd. This makes it so changing one doesn't affect the start doesn't require modifying the other parameter to preserve duration.  

elevationPitch is the main reason I looked into this in the first place. On my server this was hard coded as 16 when sent and wasn't even modifiable (ruse). This controls the arc that the projectile follows. It gets used in the target method which is basically the initialization code for the projectile:

Math.tan(elevationPitch * 0.02454369D)

The Math.tan function takes in the angle as radians and the multiplication by the 0.02454369 makes things interesting. The tan function peaks out to infinity at 90 degrees or pi/2 radians. With this scale it peaks out at exactly elevationPitch=64. As to why it's like this, I would only be pulling out of my ass rather than my brain. (Fits nicely into 6 bits though). As for getting meaning out of this value you can modify it to get the exact angle of projection you want for the projectile. Say we want 30 degrees. Then we would send elevationPitch as Math.toRadians(30) / 0.02454369D. As you increase the elevationPitch from 0 to 64, be aware that the speed of the projectile increases to keep the time of flight constant. So if you change an existing projectile's elevationPitch from 30 to 60, its speed would be much faster. If for whatever reason you want the projectile to go downwards and curve upwards (inverted rainbow) you can do so with elevationPitch in the range of (64, 128] where 128 is perfectly flat and closer to 64 is more curved. 

elevationPitch = 105


leapScale (sometimes called radius) I'll be honest I don't really know what this is entirely and it was also hardcoded as 64 for all the projectiles on ruse server sided. This specifies at which point along the arc the projectile should start. If you pass 0 it will start exactly on the starting position (which would look weird usually). The higher you make it, the further along the arc your projectile will appear when it starts. There is a point where it will go past the end point of the projectile and will start even further out than the end point and travel backwards to the end point (might be interesting for somebody doing some wacky custom content idk). This won't likely happen though when restricted to a single byte, I only found this when I was modifying the value client sided for fun and I put it pretty high. Calculating the parameters for halfway or quarterway etc of the arc with leapScale can be done, but I will probably do that at a later date since it's a bit more math and there's enough stuff here as it is. 

leapScale = 400


All of this information came from my deductions from reading the client code for projectiles which may be incorrect. Hope people find this useful

  • Thanks 2

Share this post

Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Sign in to follow this  

  • Recently Browsing   0 members

    No registered users viewing this page.

  • Create New...

Important Information

By using our forums or services as a guest you agree to our Terms of Service: Terms of Use Privacy Policy: Privacy Policy and Guidelines