[Tutorial] Custom Playerdamage system
#1

When i ******d this, i found no-one having a real tutorial of this, only some vague attempts that doesn't work properly, so i decided to make my first tutorial here about my own script.
Quote:
Table of Contents

Includes
Description
OnPlayerTakeDamage VS OnPlayerGiveDamage
Part 1 - Setting Teams
Part 2 - Get Players Health and Armour
Part 3 - Setting up the Switches
Part 4 - Applying the Custom Damages & Detecting Bodyparts

Includes

You only need the basic a_samp include.

Description

This tutorial will learn you how to create an efficient system that gives custom damages to players, depending where you shoot on their body, and more factors you can choose yourself.

• This includes custom armour damage, i saw alot of tutorials that only gave custom damages to health, not armour.

• This removes the old damage, so you can make completely new damages. I saw alot of tutorials that only added more damage to the old damage.

To do this, we use either the OnPlayerTakeDamage callback or the OnPlayerGiveDamage callback.
They have some pre-made parameters i will show you:
Code:
public OnPlayerTakeDamage(playerid, issuerid, Float: amount, weaponid, bodypart)
// playerid = The player that got damaged.
// issuerid = The player or thing that gave the damage.
// Float: amount = The amount of damage given.
// weaponid = The weapon ID that dealt the damage, may also be other reasons which can be seen on the wiki.
// bodypart = The bodypart that was hit.

public OnPlayerGiveDamage(playerid, damagedid, Float: amount, weaponid, bodypart)
// playerid = The person that gave the damage.
// damagedid = The player that got damaged.
// Float: amount = The amount of damage given.
// weaponid = The weapon ID that dealt the damage.
// bodypart = The bodypart that was hit.
For the sake of this tutorial we will be using OnPlayerTakeDamage.

OnPlayerTakeDamage VS OnPlayerGiveDamage

OnPlayerTakeDamage is called when a player takes damage on their screen, while OnPlayerGiveDamage is called when an attacker deals damage to another player on the attackers screen.

The upside with OnPlayerTakeDamage is that the damage always looks fair on both clients screens.
Although the aggressor may shoot the player where the callback isn't called because the bullet or punch doesn't hit the victim on their screen.
This also leads to the weird SAMP sync during melee fights where sometimes nobody takes damage when hitting eachother.

OnPlayerGiveDamage gives a much more accurate feel for the attacking player, although the bullets or punches doesn't always hit on the victim's screen the callback will still be called, as long as they hit on the aggressors screen.
This is arguable a way to fix the SAMP sync during melee fights, since the player always deals damage when punching the other player.
The other player doesn't necessarily see it on their screen though, and both players can end up continuously damaging eachother at the same time.

Part 1 - Setting Teams

First off, we need to assign the player to the same teams, which makes them unable to damage eachother, then we can use our own damages instead.
We do this with the SetPlayerTeam function when the player connects.
Code:
public OnPlayerConnect(playerid)
{
    SetPlayerTeam(playerid, 0);
    return 1;
}
Part 2 - Get Players Health and Armour

Before adding weapon damages, we need to get the players health and armour, then store it in 2 different floats, floats are tagged variables you can store different information in, like health, armour, coordinates and such. If you need more info, you can read more about it on the wiki.

So we declare the variables, but we need to declare them as floats, so we put "Float:variablename".
You can name the floats whatever you want, it doesn't matter.

I called them "health" and "armour", so now our OnPlayerTakeDamage callback should look like underneath.
Code:
public OnPlayerTakeDamage(playerid, issuerid, Float:amount, weaponid, bodypart)
{
    new
        Float: health,
        Float: armour
    ; // Declares the variables, and defines them as floats.
    return 1;
}
But we have to store the health and armour of the player in these floats, as of now we just created them.
To store the players health and armour in these floats, use GetPlayerHealth(playerid, Float:variablename) and GetPlayerArmour(playerid, Float:variablename)

Code:
public OnPlayerTakeDamage(playerid, issuerid, Float:amount, weaponid, bodypart)
{
    new
        Float: health,
        Float:armour
    ; // Declares the variables, and defines them as floats.
    GetPlayerHealth(playerid, health); // Gets the health of the player that took damage, and stores it in the float "health".
    GetPlayerArmour(playerid, armour); // Gets the armour of the player that took damage, and stores it in the float "armour".
    return 1;
}
Now we have the players health and armour, which we will need later.

Part 3 - Setting up the Switches

Now we need the weapon ID that the "issuer" attacked the player with.
Quite handy, it's already declared in the variable as "weaponid".

So to set up different damages for each weapon, we use a switch to detect which system to use.
I see alot of people using this method:
Code:
if(weaponid == 24) // If the weapon is an Desert Eagle (id 24)
{
    // Codes
}
else if(weaponid == 31) // If the weapon is an M4 (id 31)
{
    // Codes
}
else if(weaponid == 34) // If the weapon is an Sniper (id 34)
{
    // Codes
}
You can do this, but this method is very inefficient and slow.
It is both easier, more efficient, and faster to use switches, as the wiki also states, this counts for everything where you would use an "else" or "else if", if it's possible, use a switch instead.
To create a switch, we set it up like this:
Code:
public OnPlayerTakeDamage(playerid, issuerid, Float: amount, weaponid, bodypart)
{
    new
        Float: health,
        Float:armour
    ; // Declares the variables, and defines them as floats.
    GetPlayerHealth(playerid, health); // Gets the health of the player that took damage, and stores it in the float "health".
    GetPlayerArmour(playerid, armour); // Gets the armour of the player that took damage, and stores it in the float "armour".

    switch(weaponid) // Starts the switch, and switches through the variable "weaponid". (declared in callback)
    {
        case 24: // If the variable is set to 24 (Desert Eagle weaponid), it activates the codes below.
        {
            // Codes
        }
        case 31: // If the variable is set to 31 (M4 weaponid), it activates the codes below.
        {
            // Codes
        }
        case 34: // If the variable is set to 34 (Sniper weaponid), it activates the codes below.
        {
            // Codes
        }
    }
    return 1;
}
NOTE: All weapon ID's can be found here.

So now we'll work with the Desert Eagle weapon. And i already showed you how to set up a switch to detect it. (And other weapons.)
If you want more weapons, just put more "Case id:", and replace "id" with the weaponid ofcourse.

But now we need to check if the player has armour, or if he doesn't, and then deal damage to his armour, or health.
We use a switch again, to check if his armour is 0, then we deal damage to his health. And we use "default" which replaces the "else", so if his armour isn't 0, it will activate the "default" code.
We create the switch inside the case 24: (Desert Eagle weapon), like this:
Code:
case 24: // Desert Eagle weapon ID
{
    switch(armour) // Creates a switch that switches through the armour float, and checks the value.
    {
        case 0: // If the value is 0, the codes underneath will activate.
        {
            // Codes
        }
        default: // Otherwise the codes underneath will activate.
        {
            // Codes
        }
    }
}
Those are the basic switches done, in part 4 i explain how to give damage according to which bodypart was hit.

Part 4 - Applying the Custom Damages & Detecting Bodyparts

Now we need to get the bodyparts, and deal damage accordingly to which bodypart was hit.
Quote:
Originally Posted by SA-MP Wiki

Important
Note
These IDs are not 100% confirmed, and are not defined in any SA-MP includes - they must be defined by the scripter. It is unknown if IDs 0, 1 and 2 have any use.
So therefore we need to define the bodyparts ourselves, by putting this at the top of our script.
Code:
#define BODY_PART_TORSO 3
#define BODY_PART_GROIN 4
#define BODY_PART_LEFT_ARM 5
#define BODY_PART_RIGHT_ARM 6
#define BODY_PART_LEFT_LEG 7
#define BODY_PART_RIGHT_LEG 8
#define BODY_PART_HEAD 9
There's no body part 0, 1 or 2 as of now.
So the case 0 is called when the player doesn't have armour, therefore we deal damage to the players health, according to which bodypart was hit.
So we create a switch, that switches through the value of "bodypart", which is created in the callback, and we defined.
Code:
switch(bodypart) // Under case 0.
{
    case 3: SetPlayerHealth(playerid, health - 30); // Torso.
    case 4: SetPlayerHealth(playerid, health - 20); // Groin.
    case 5: SetPlayerHealth(playerid, health - 5);  // Left Arm.
    case 6: SetPlayerHealth(playerid, health - 10); // Right Arm.
    case 7: SetPlayerHealth(playerid, health - 7);  // Left Leg.
    case 8: SetPlayerHealth(playerid, health - 12); // Right Leg.
    case 9: SetPlayerHealth(playerid, health - 50); // Head.
}
These values obviously aren't realistic, but i can use them to describe, so this will check if the case is etc. 4, (BODY_PART_GROIN) and SetPlayerHealth to his current health, minussed with 20.
It will do accordingly with the other cases, such as 9, (BODY_PART_HEAD) it will set his health to his current health, minussed with 50.
Ofc. you can change the damages it will minus the health with.
And then you do the same under "default", just change SetPlayerHealth to SetPlayerArmour, and health - (value) to armour - (value).



And you're done!
By now you should hopefully have a fully working custom damage system for your servers, if you have any questions, feel free to ask about it below.
Reply
#2

Not bad, Good job.
Reply
#3

Simple, easy good job
Reply
#4

Good job on the effort.
Reply
#5

Thanks guys! Took a long time to make this.
Reply
#6

Amazing tutorial you got here.

Just a quick question, lets say I want the player who got shot in the leg unable to run, how do I stop them from sprinting?
Reply
#7

Quote:
Originally Posted by JaydenJason
View Post
Amazing tutorial you got here.

Just a quick question, lets say I want the player who got shot in the leg unable to run, how do I stop them from sprinting?
Check for running animation or detect keys while they're on foot. So in case if the player has got a shot on leg, you can detect and avoid it by setting some other animation like using walk or fall.
Reply
#8

Quote:
Originally Posted by JaydenJason
View Post
Amazing tutorial you got here.

Just a quick question, lets say I want the player who got shot in the leg unable to run, how do I stop them from sprinting?
Thanks Jayden.

For the leg function, start off by creating a variable called etc. "Injured[MAX_PLAYERS]".
Then under OnPlayerTakeDamage, check if the bodypart hit was one of his legs, if so, set the variable to 1.
pawn Code:
Injured[playerid] = 1; // Sets the players variable to 1.
For the holding/releasing key-codes just use these 2 defines to make it less confusing
pawn Code:
#define HOLDING(%0) \
        ((newkeys & (%0)) == (%0))

#define RELEASED(%0) \
        (((newkeys & (%0)) != (%0)) && ((oldkeys & (%0)) == (%0)))
Then under OnPlayerKeyStateChange, check if he's holding a direction key (WASD by default) and the sprint key at the same time.
pawn Code:
if(HOLDING(KEY_UP) || HOLDING(KEY_LEFT) || HOLDING(KEY_RIGHT) || HOLDING(KEY_DOWN) && HOLDING(KEY_SPRINT))
Then check if that Injured variable is set to 1, and that he's not in a vehicle, if so, set his animation to a running animation.
pawn Code:
if(Injured[playerid] = 1 && !IsPlayerInAnyVehicle(playerid)) ApplyAnimation(...);
Then finally, stop the running animation when he releases KEY_SPRINT.
pawn Code:
if(RELEASED(KEY_SPRINT)) ApplyAnimation(playerid, "CARRY", "crry_prtial", 4.0, 0, 0, 0, 0, 0);
That animation cancels other animations, but not his running/speed i don't think.
You could also use "ClearAnimations(playerid);", but that would ruin his running speed.
Reply
#9

Much appreciated Calvin, thanks for the tut on the running thingy!
Reply
#10

FK FK FK

FFS WRONG THREAD,
I'm addicted to it
Reply
#11

Good job. Very useful tutorial!
Reply
#12

Quote:
Originally Posted by JaydenJason
View Post
Much appreciated Calvin, thanks for the tut on the running thingy!
Quote:
Originally Posted by kamiliuxliuxliux
View Post
Good job. Very useful tutorial!
Thanks guys.
Reply
#13

Thank you very helpful for me.
Reply
#14

Well explained and you added a decent layout to it.
Good job!
Reply
#15

I think this is great, Good job buddy.
Reply
#16

Nice job, Respect+
Reply
#17

Good job detailed and useful tutorial.
Reply
#18

Good job with this tutorial! I really needed that animation part
Reply
#19

There are hacks which prevent OnPlayerTakeDamage from being called.
Reply
#20

Quote:
Originally Posted by Tamer
View Post
There are hacks which prevent OnPlayerTakeDamage from being called.
True, Even though, Deagle/sniper damage are high because Running weapons can defeat Walking weapons easliy (if both in same skills)
So i don't think i will modify the damage.
Reply


Forum Jump:


Users browsing this thread: 3 Guest(s)