[Tutorial] [TuT]How to create a Vehicle Shooting (laser) + Some [Trigonometry]
#1

Introduction

Hello everyone. Well i've created a filterscript some months/years ago, In which a player can shoot laser type things from his Vehicle and any vehicle infront of the laser in a specific range would get Destroyed...

This is a video showing it. So you know what i'll be explaining

[ame]http://www.youtube.com/watch?v=94hVFJssrjw[/ame]

This can be used for other things aswell, Like throwing knife and many more things. Though might need to be edited a bit.

Plus: This is my first tutorial, So if there's anything wrong, I'm sorry. ^^

What we Need

pawn Code:
#include <a_samp>
Only this

What it actually does is loads the Include a_samp, so we can access different functions that a_samp have in it.

Lets Get Started

At the top of the script we might want to add
pawn Code:
new FireShot[MAX_PLAYERS];
new gRocketObj[MAX_PLAYERS];
The new declares a new variable and the FireObject[MAX_PLAYERS]; is so we know if player shot the laser or not, I'll explain later when the time comes.

so

pawn Code:
new FireShot[MAX_PLAYERS];
new gRocketObj[MAX_PLAYERS];
So this will create a new Variable with name FireShot and gRocketObj and their values will be 0 (It is automatically 0 if no value is given)

The [MAX_PLAYERS]; is if you have
pawn Code:
#define MAX_PLAYERS 500
So the variable will be FireShot[500];
It can be setted as FireShot[playerid] = 1; or 0; for different players.
I will tell later for what is it used.

Now moving on.
pawn Code:
public OnPlayerKeyStateChange(playerid, newkeys, oldkeys)
Just like the name, This callback is called whenever a player key state changes, Either pressed or released or held.
playerid = The id of the player who pressed the keys
newkeys = The keys currently held
oldkeys = The keys that were just released.

e.g if i press LMB (KEY_FIRE), it's newkeys untill i release it then it's oldkeys.
pawn Code:
if(IsPlayerInAnyVehicle(playerid) && FireShot[playerid] == 0 && newkeys & 4 && !IsValidObject(gRocketObj[playerid]))
pawn Code:
if(IsPlayerInAnyVehicle(playerid)
If the player 'playerid' is in Any vehicle, 'playerid' is the player whose key state was changed.

pawn Code:
&& FireShot[playerid] == 0
&& = And, FireShot[playerid] == 0, if player haven't shot the fire. We set the value to 1 when player shots fire, so this detects if it's 0 means player haven't shot any fire.

pawn Code:
&& newkeys & 4
And if the new keys are KEY_FIRE, you can see HERE that they value of KEY_FIRE is 4, so we can use 4.
pawn Code:
&& !IsValidObject(gRocketObj[playerid]))
Only run the code if the object doesn't already exist, otherwise more objects will take up gRocketObj and the previous ones won't be deleted. The [playerid] means if the object is created for the specific playerid. I've added ! if the objct is Not valid.
Note: ! = Not.
pawn Code:
public OnPlayerKeyStateChange(playerid, newkeys, oldkeys)
{
    if(IsPlayerInAnyVehicle(playerid) && FireShot[playerid] == 0 && newkeys & 4 && !IsValidObject(gRocketObj[playerid]))   // Only run the code if the object doesn't already exist, otherwise more objects will take up gRocketObj and the previous ones won't be deleted
    {
        SetPlayerTime(playerid,0,0);
Settings the player time.
SetPlayerTime(playerid, Hour '0', minute '0');

pawn Code:
new
            vehicleid = GetPlayerVehicleID(playerid),
            Float:x,
            Float:y,
            Float:z,
            Float:r,
            Float:dist = 50.0,
            Float:tmpang,
            Float:tmpx,
            Float:tmpy,
            Float:tmpz;
Creating new Variables,
pawn Code:
vehicleid = GetPlayerVehicleID(playerid)
assigning the variable vehicleid to ' = ' GetPlayerVehicleID ' Get's player vehicle id' (playerid)
So this gets the id of the vehicle the player currently is in.
And the floats: x,y,z etc
Code:
SAMP WIKI
Floats are a type of tagged variable in PAWN and they are "floating point numbers", hence the name Float. This basically means that they support decimal places, such as 1.2, 1.1, 5.32, 64.21 - where as normal integers (untagged) don't support decimal places.
So we are going to be using these variables to get player pos and target pos and do some trigonometry to detect the player infront of you.

pawn Code:
public OnPlayerKeyStateChange(playerid, newkeys, oldkeys)
{
    if(IsPlayerInAnyVehicle(playerid) && FireShot[playerid] == 0 && newkeys & 4 && !IsValidObject(gRocketObj[playerid]))   // Only run the code if the object doesn't already exist, otherwise more objects will take up gRocketObj and the previous ones won't be deleted
    {
        SetPlayerTime(playerid,0,0);
        new
            vehicleid = GetPlayerVehicleID(playerid),
            Float:x,
            Float:y,
            Float:z,
            Float:r,
            Float:dist = 50.0,
            Float:tmpang,
            Float:tmpx,
            Float:tmpy,
            Float:tmpz;

        FireShot[playerid] = 1;
        SetTimerEx("ShotFire", 1000, 0, "i", playerid);
        GetVehiclePos(vehicleid, x, y, z);
        GetVehicleZAngle(vehicleid, r);
pawn Code:
FireShot[playerid] = 1;
Setting the variable FireShot to 1 for [playerid] (The player who's keystate changed)

pawn Code:
SetTimerEx("ShotFire", 1000, 0, "i", playerid);
We set a timer ShotFire, which will be called after 1 second.
"ShotFire" the function name, that we later create a callback with.
"1000" is the time which is equal to 1 sec
" 0 " , is the repeating which we set to = false ( 0 is false, 1 is true)
and the " i " and playerid
-------Wiki,--------
"i" Special format indicating the types of values the timer will pass.
"playerid" Indefinite number of arguments to pass (must follow format specified in previous parameter).

We created this timer so after 1 sec we call the callback ShotFire and then in that callback we set FireShot[playerid] to 0 so when player tries to shot again, He will be able to shoot as the script detects if FireShot is 0 then continue , But if he tries to shot just suddenly after he shot one laser, He couldn't do it. So he have to wait 1 sec before shooting again.

pawn Code:
GetVehiclePos(vehicleid, x, y, z);
Getting the vehicle positions and storing it into the variables we created 'x' 'y' 'z'

pawn Code:
GetVehicleZAngle(vehicleid, r);
Self explanatory , Get the rotation of a vehicle on the Z axis.
vehicleid, The id of the vehicle which angle we get and we store it into ' r ' .

pawn Code:
new rand = random(12);
creating new variable rand and assigning/setting it to random(12);
A random number ranging from 0 to max-1.
random (max) in our case max is 12 so it can be used to generate a number ranging from 0 to 11.


pawn Code:
switch(rand)
Using switch to switch between rand which is equal to random(12) which is equal to 11, so we can use 11 cases from case 0 to 11, ^^...

pawn Code:
switch(rand)
        {
            case 0: gRocketObj[playerid] = CreateObject(18647, x, y, z, 0, 0, r);
            case 1: gRocketObj[playerid] = CreateObject(18648, x, y, z, 0, 0, r);
            case 2: gRocketObj[playerid] = CreateObject(18649, x, y, z, 0, 0, r);
            case 3: gRocketObj[playerid] = CreateObject(18650, x, y, z, 0, 0, r);
            case 4: gRocketObj[playerid] = CreateObject(18651, x, y, z, 0, 0, r);
            case 5: gRocketObj[playerid] = CreateObject(18652, x, y, z, 0, 0, r);
            case 6: gRocketObj[playerid] = CreateObject(18647, x, y, z, 0, 0, r+90);
            case 7: gRocketObj[playerid] = CreateObject(18648, x, y, z, 0, 0, r+90);
            case 8: gRocketObj[playerid] = CreateObject(18649, x, y, z, 0, 0, r+90);
            case 9: gRocketObj[playerid] = CreateObject(18650, x, y, z, 0, 0, r+90);
            case 10: gRocketObj[playerid] = CreateObject(18651, x, y, z, 0, 0, r+90);
            case 11: gRocketObj[playerid] = CreateObject(18652, x, y, z, 0, 0, r+90);
        }
So the switch(random) code will choose any of the cases above and each of the case have different object and rotations, So it'll look much better.
pawn Code:
case 0: gRocketObj[playerid] = CreateObject(18657, x, y, z, 0, 0, r);
case 0 - If the random code selected case 0, then do whatever we wrote in case 0:
gRocketObj[playerid] = 'Assigning the gRocketObject for playerid to CreateObject
CreateObject - Create an object
pawn Code:
CreateObject(modelid, Float:x, Float:y, Float:z, Float:rx, Float:ry, Float:rz, Float:DrawDistance);
Pretty self explanatory, CreateObject of ModelID (1864x in our case) at the pos x,y,z which is the x , y , z in which we stored player pos and r is rotation, Which is 'GetVehicleZAngle' the rotation of vehicle on z axis, so it'll set the object facing to the place where the vehicle is facing.

pawn Code:
for(new i;i<MAX_PLAYERS;i++)
Looping through MAX_PLAYERS, if you have #define MAX_PLAYERS 50 it means looping through 50 players.
Though i'd suggest to use foreach much faster + no need to check for if player we loop through is connected or not, if not skip him.

Quote from Y_LESS
Code:
Note that this IS NOT a simple definition to insert the normal loop, it's actually a completely different implementation using lists of players, making it faster that the normal system. Furthermore, because of the way it works, it doesn't matter what MAX_PLAYERS is, whether it's correct for your server or not, this loop will always take the same amount of time as it ONLY loops through connected players, not unconnected players.
But sadly i'm not using it , But i hardly suggest to use it if anyone uses this script, Just change the loops to foreach one.

pawn Code:
for(new i;i<MAX_PLAYERS;i++)
        {
            if(IsPlayerConnected(i)
            if(i == playerid)continue;
            if(IsPlayerInRangeOfPoint(i, 50.0, x, y, z))

if(IsPlayerConnected(i)) - if the player we loop for is connected then continue.
if(i == playerid)continue; = if i (the loop) is equal to playerid, then continue

pawn Code:
if(IsPlayerInRangeOfPoint(i, 50.0, x, y, z))
If the player ( i ) is in range of point, 50.0 (The range) x, y, z the position of Object.

pawn Code:
if(IsPlayerInRangeOfPoint(i, 50.0, x, y, z))
            {
                GetPlayerPos(i, tmpx, tmpy, tmpz);
                tmpang = (90-atan2(tmpy-y, tmpx-x));
                if(tmpang < 0)tmpang = 360.0+tmpang;
                tmpang = 360.0 - tmpang;
                if(floatabs(tmpang-r) < 5.0)
                {
                    dist = GetPlayerDistanceFromPoint(i, x, y, z);
                }
Here i loop through all players and check if someone is in the line of sight of the missile (by 5 degrees) and adjust the missile so it hits the target if the player is in range of the missile (50.0)

pawn Code:
for(new i;i<MAX_PLAYERS;i++)
        {
            if(IsPlayerConnected(i))
            if(i == playerid)continue;
            if(IsPlayerInRangeOfPoint(i, 50.0, x, y, z))
            {
                GetPlayerPos(i, tmpx, tmpy, tmpz);
                tmpang = (90-atan2(tmpy-y, tmpx-x));
                if(tmpang < 0)tmpang = 360.0+tmpang;
                tmpang = 360.0 - tmpang;
                if(floatabs(tmpang-r) < 5.0)
                {
                    dist = GetPlayerDistanceFromPoint(i, x, y, z);
                }
            }
        }
        MoveObject(gRocketObj[playerid],x + (dist * floatsin(-r, degrees)),y + (dist * floatcos(-r, degrees)),z,100.0);                        
    }
}
Then here is the MoveObject, If a player is near the object when it's moving,
pawn Code:
MoveObject(gRocketObj[playerid],x + (dist * floatsin(-r, degrees)),y + (dist * floatcos(-r, degrees)),z,100.0);
MoveObject moves the object, gRocketObj[playerid] is the object we created for player and the rest is trigonometry.
I'll explain it, Thanks and credits to Southclaw for helping me with it.

You must have a bit of trigonometry knowledge...

trig is kinda simple, to understand how this code properly works you have to understand how trigonometry works. Which is fairly simple yet hard to grasp at first.

trigonometry simply means calculations with triangles (that’s where the tri comes from).
tri = 3, gono = angles, metry = measurements if i'm not wrong :P .

The application in SA:MP is to effectively get the two straight sides of a theoretical right angle triangle.


Let's get started with Trigonometry for samp?



This image shows how it works, the diagonal line represents the direction (fAng = Angle of vehicle or player or whatever) You can see the radius of this (based on the distance, which is 0.5, I realise now that is a bad choice for an example)

The angle you take from players/vehicles is the offset angle from north (0 degrees) this is the angle you use. Now can you see two triangles in that diagram? Two right angled triangles on either side of the direction line if you join up the projected point to the X and Y axis

You can use the trig functions on those theoretical triangles to get the lengths of the sides, now there's a way of working out what you need to use but that's used more in Geometry (You'll lean that if you take a math class or something!) Just remember it like this for SA:MP:

(Keep in mind SA:MP coordinates are in order: XY(Z) (ignore Z for now)

Anyway, the next diagram shows what the Sine function does to the angle:



As you can see, sin-ing the angle will give you the "height" of the triangle, this is just a distance on the X axis, which is really handy because now you can add that distance to the Origin X coordinate!

And at the cosine function

When you input the angle it returns that small distance from the origin to the projected point on the Y axis, now you can simply add that value to the Origin Y coordinate and there you go!

With the X modified with floatsin and Y modified with floatcos you now have a projected position based on an angle!

Wondering how to project it a certain distance? Well, I made a huge mistake in the images, the distance should be 1.0 NOT 0.5 (Just pretend it's 1.0!)
When you do this sin/cos stuff to coordinates, the end result will be always 1 unit from the origin. Now this is useful because now you can simply multiply those sine and cosine values by any unit value to project it a specific distance:

pawn Code:
X + (5.0 * floatsin(fAng)), Y + (5.0 * floatcos(fAng))
// This will make the projected position 5.0 meters away.
This also works with values below 1.0:

pawn Code:
X + (0.5 * floatsin(fAng)), Y + (0.5 * floatcos(fAng))
// This will make the projected position 0.5 meters away.
And negative values will make the projected position behind the origin point:

pawn Code:
X + (-5.0 * floatsin(fAng)), Y + (-5.0 * floatcos(fAng))
// This will make the projected position 5.0 meters behind the origin.


You can alter the angle too to produce different results, for instance adding 90.0 to the angle will make the projected position on the right hand side* of the origin:


pawn Code:
X + (5.0 * floatsin(fAng + [U]90.0[/U])), Y + (5.0 * floatcos(fAng + [U]90.0[/U]))
*Player/vehicle positions are inverted for some reason, adding 90 will make it on the left I think.


pawn Code:
forward ShotFire(playerid);
public ShotFire(playerid)
{
        FireShot[playerid] = 0;
        return 1;
}
Now if you remember we created a timer, That was ShotFire...
pawn Code:
forward ShotFire(playerid);
It's important, You have to forward all public functions, I'm not sure why.

So the code above sets the FireShot[playerid] (=) to 0, So the script/code knows that 1 second is passed, now the player can shot lasers again, I've created this timer so vehicles can shot only 1 laser in 1 second.
return 1; returning true ( 1=true, 0=false)

And the last part, Thanks god...
pawn Code:
public OnObjectMoved(objectid)
{
        for(new i;i<MAX_PLAYERS;i++)
        {
                if(objectid == gRocketObj[i])
                {
                    new
                                Float:x,
                                Float:y,
                                Float:z;

                    GetObjectPos(gRocketObj[i], x, y, z);
                    CreateExplosion(x, y, z, 11, 3.0);
                    DestroyObject(gRocketObj[i]);
                }
        }
}
pawn Code:
public OnObjectMoved(objectid)
This callback is called whenever a Moving object stops moving or finishes moving,
The object id is the id of the object that stopped moving, It's not the modelid though..

pawn Code:
for(new i;i<MAX_PLAYERS;i++)
Ah again, I suggest using foreach , again...
Looping through all players 'MAX_PLAYERS' , I suggest adding #define MAX_PLAYERS (your slots amount) at top, i don't know but i think that's much better...

pawn Code:
if(objectid == gRocketObj[i])
If the objectid that moved is the object that we stored in gRocketObj variable, Which we created at OnPlayerStateChange and [i] is the specific playerid who created the object

pawn Code:
new
                        Float:x,
                        Float:y,
                        Float:z;
Creating new Float variables , Float = Floating, i think i've explained before.
naming it Float: x, y and z it'll be used to get the object position of gRocketObj

pawn Code:
GetObjectPos(gRocketObj[i], x, y, z);
Getting the object (gRocketObj[i], x, y, z) position.
so the x, y, z position of gRocketObj is stored in Float: x, Float:y, Float:z variable

pawn Code:
CreateExplosion(x, y, z, 11, 3.0);
                    DestroyObject(gRocketObj[i]);
We Create an explosion at x, y, z (the gRocketObj position) with range and type
https://sampwiki.blast.hk/wiki/CreateExplosion

So the overall script looks like this
pawn Code:
#include <a_samp>


new FireShot[MAX_PLAYERS];
new gRocketObj[MAX_PLAYERS];

public OnPlayerKeyStateChange(playerid, newkeys, oldkeys)
{
    if(IsPlayerInAnyVehicle(playerid) && FireShot[playerid] == 0 && newkeys & 4 && !IsValidObject(gRocketObj[playerid]))   // Only run the code if the object doesn't already exist, otherwise more objects will take up gRocketObj and the previous ones won't be deleted
    {
        SetPlayerTime(playerid,0,0);
        new
            vehicleid = GetPlayerVehicleID(playerid),
            Float:x,
            Float:y,
            Float:z,
            Float:r,
            Float:dist = 50.0,
            Float:tmpang,
            Float:tmpx,
            Float:tmpy,
            Float:tmpz;

        FireShot[playerid] = 1;
        SetTimerEx("ShotFire", 1000, 0, "i", playerid);
        GetVehiclePos(vehicleid, x, y, z);
        GetVehicleZAngle(vehicleid, r);
        new rand = random(12);
        switch(rand)
        {
            case 0: gRocketObj[playerid] = CreateObject(18647, x, y, z, 0, 0, r);
            case 1: gRocketObj[playerid] = CreateObject(18648, x, y, z, 0, 0, r);
            case 2: gRocketObj[playerid] = CreateObject(18649, x, y, z, 0, 0, r);
            case 3: gRocketObj[playerid] = CreateObject(18650, x, y, z, 0, 0, r);
            case 4: gRocketObj[playerid] = CreateObject(18651, x, y, z, 0, 0, r);
            case 5: gRocketObj[playerid] = CreateObject(18652, x, y, z, 0, 0, r);
            case 6: gRocketObj[playerid] = CreateObject(18647, x, y, z, 0, 0, r+90);
            case 7: gRocketObj[playerid] = CreateObject(18648, x, y, z, 0, 0, r+90);
            case 8: gRocketObj[playerid] = CreateObject(18649, x, y, z, 0, 0, r+90);
            case 9: gRocketObj[playerid] = CreateObject(18650, x, y, z, 0, 0, r+90);
            case 10: gRocketObj[playerid] = CreateObject(18651, x, y, z, 0, 0, r+90);
            case 11: gRocketObj[playerid] = CreateObject(18652, x, y, z, 0, 0, r+90);
        }
        for(new i;i<MAX_PLAYERS;i++)
        {
            if(IsPlayerConnected(i))
            if(i == playerid)continue;
            if(IsPlayerInRangeOfPoint(i, 50.0, x, y, z))
            {
                GetPlayerPos(i, tmpx, tmpy, tmpz);
                tmpang = (90-atan2(tmpy-y, tmpx-x));
                if(tmpang < 0)tmpang = 360.0+tmpang;
                tmpang = 360.0 - tmpang;
                if(floatabs(tmpang-r) < 5.0)
                {
                    dist = GetPlayerDistanceFromPoint(i, x, y, z);
                }
            }
        }
        MoveObject(gRocketObj[playerid],x + (dist * floatsin(-r, degrees)),y + (dist * floatcos(-r, degrees)),z,100.0);                             // Nice and fast!
    }
}



forward ShotFire(playerid);
public ShotFire(playerid)
{
        FireShot[playerid] = 0;
        return 1;
}

public OnObjectMoved(objectid)
{
        for(new i;i<MAX_PLAYERS;i++)
        {
                if(objectid == gRocketObj[i])
                {
                    new
                        Float:x,
                        Float:y,
                        Float:z;

                    GetObjectPos(gRocketObj[i], x, y, z);
                    CreateExplosion(x, y, z, 11, 3.0);
                    DestroyObject(gRocketObj[i]);
                }
        }
}
Reply
#2

Excellent! very detailed, will read it all when I have some time.
Reply
#3

Quote:
Originally Posted by Onfroi
View Post
Excellent! very detailed, will read it all when I have some time.
EDIT: Ok i've completed the tutorial. :L
Reply
#4

Awesome Rudy , pro style .
Reply
#5

Trigonometry (Mind = Blown).. Anyways, nice tutorial!
Reply
#6

Excellent work!
Reply
#7

Thank you.
Reply
#8

Awesome tutorial thank you!
Reply
#9

Awesome Tutorial
Reply
#10

Amazing.
Reply
#11

Thank you, I'm not doing anything these days, So if there's anything you would like to see a tutorial on Please pm me

Thanks again.
Reply
#12

your welcome so best of luck if your having any another projects
Reply
#13

Excellent tutorial, very detailed indeed.
Reply
#14

Nice +rep
Reply
#15

Thank you
Reply
#16

Awesome tutorial i wish i can make some
repped++
but u forgot to mention the angles in samp is measured counterclockwise
Quote:

Angles are reversed in GTA:SA; 90 degrees would be East in the real world, but in GTA:SA 90 degrees is in fact West. North and South are still 0/360 and 180. To convert this, simply do 360 - angle.

Reply
#17

Nice work. Keep it up.
Reply
#18

Thanks, if anything is unclear, you know what to do
Reply
#19

great work!! usefull
Reply
#20

Well done bro! Had a great fun with it Well done and well explained.
Reply


Forum Jump:


Users browsing this thread: 3 Guest(s)