Raised This Month: $51 Target: $400
 12% 

Traceline Tutorial (Fakemeta)


Post New Thread Reply   
 
Thread Tools Display Modes
Author Message
Nomexous
Member
Join Date: Oct 2007
Old 01-23-2008 , 01:10   Traceline Tutorial (Fakemeta)
Reply With Quote #1

I've recently found myself using traceline a lot in my plugins. I realized that there weren't any tutorials so I decided to write one.

Theory

What is traceline, really? Traceline is a function in the Half-Life engine which is used to draw lines from one point to another in a 3D setting. It can return information, like who (or what) was hit, and various other useful bits of info.

Traceline is used in every mod in order to determine where a player is looking and shooting. Therefore, it's quite useful to learn how to manipulate traceline to your liking.

The mod you're scripting for (in my case, CS) is separate from the HL engine. Metamod (and, by extension, AMX Mod X) sits between the mod DLL and the HL engine. This gives us the ability to intercept anything the mod DLL sends to the engine, and visa versa. So, when CS asks the HL engine to calculate a traceline, we can completely change the outcome.

The Code

We will use forwards in Fakemeta in capture the communication between CS and the HL engine. This means we'll see the data CS was going to pass the engine in order to calculate a traceline.
PHP Code:
#include <amxmodx>
#include <fakemeta>

public plugin_init()
{
    
register_plugin("Traceline Tutorial""1.0""Nomexous")
    
    
register_forward(FM_TraceLine"traceline_forward")
}

public 
traceline_forward(Float:start[3], Float:end[3], conditionsidtrace)
{
    
// Our traceline forward.

The meaning of the arguments forwarded to our function may not be entirely clear, so:

Float:start[3] The origin from which the traceline starts.

Float:end[3] The origin the traceline is drawn to.

conditions Specifies whether or not to ignore certain entities when drawing the traceline. Values can be DONT_IGNORE_MONSTERS, IGNORE_GLASS, IGNORE_MISSLE, and IGNORE_MONSTERS.

id This is the entity to ignore when drawing the traceline. Presumably, you're drawing from the origin of one entity (which is inside it) to another point. With player tracelines, id is the player index of whomever is firing the traceline.

trace Our trace result. This is the most important part. It holds the information about what was hit, among other things. Use get_tr2 and set_tr2 with this.

The Trace Result

This is the most exciting part. The variable trace holds detailed information, which can be retreived as outlined:
PHP Code:
public traceline_forward(Float:start[3], Float:end[3], conditionsidtrace)
{
    
// There are more TR_* constants than are listed here; these are the
    // ones I use most.
    
    
new hit get_tr2(traceTR_pHit)
    
// What was hit by the traceline. It will either be a player index,
    // entity index, 0 (part of map), or -1 (didn't hit anything; 
    // doesn't happen with player tracelines).
    
    
new hitgroup get_tr2(traceTR_iHitgroup)
    
// If the traceline hit another player, returns will be HIT_HEAD,
    // HIT_CHEST, HIT_LEFTLEG... etc. If the traceline hit part of the
    // map, this returns HIT_GENERIC.
    
    
new Float:fraction
    get_tr2
(traceTR_flFractionfraction)
    
// Returns a number between 0.0 and 1.0, indicating how far the
    // traceline traveled start to end before it hit something. Depending
    // on what conditions were passed to this traceline forward function,
    // it could either be a wall or another entity.
    
    
new Float:end_origin[3]
    
get_tr2(traceTR_vecEndPosend_origin)
    
// The official end of the traceline. Not necesarily the same as the
    // second argument passed to this traceline forward function.
    
    
new Float:normal[3]
    
get_tr2(traceTR_vecPlaneNormalnormal)
    
// Returns a 1 unit long vector normal to the spot that it hit. Note
    // that "normal" has a special connotation here. It doesn't mean "regular."

(Check this out if you don't know what we mean by "normal" in this context.)

Now, changing these value is as easy as using the set_tr2 function.

A Potentially Evil Plugin

I'm sure you've all played on a server which allowed headshots only. Good practice; body shots do no damage, headshots kill instantly, yadda yadda. But what if we could redirect a shot on any part of the body to the head? Foot shot turns into a headshot? Amazing!
PHP Code:
public traceline_forward(Float:start[3], Float:end[3], conditionsidtrace)
{
    
set_tr2(traceTR_iHitgroupHIT_HEAD)
    return 
FMRES_IGNORED

It's quite amusing, really. Try it. If you're used to seeing blood spurt out of head on a headshot, you'll be amazed at the blood spurting out of the foot on a foot/headshot. Shotguns are extra deadly; multiple pellets are all redirected to the head at the same time. Do some if (is_user_admin(id)), and you have yourself an evil plugin.

But it gets kinda hard trying to hide the fact that the blood from a headshot came out of the foot, or stomach, or whatever part of the body you happened to hit. What you want is for blood to also be redirected to the head. Because this relies on traceline, we are still fully in control:
PHP Code:
public traceline_forward(Float:start[3], Float:end[3], conditionsidtrace)
{
    
set_tr2(traceTR_iHitgroupHIT_HEAD// Redirect shot to head
    
    // Variable angles doesn't really have a use here.
    
static hitFloat:head_origin[3], Float:angles[3]
    
    
hit get_tr2(traceTR_pHit// Whomever was shot
    
engfunc(EngFunc_GetBonePositionhit8head_originangles// Find origin of head bone (8)
    
    
set_tr2(traceTR_vecEndPoshead_origin// Blood now comes out of the head!
    
    
return FMRES_IGNORED

This is even more evil. Now you can run around your own server, endlessly headshotting, no unexplainable blood. Though now you might need to explain why you can't do anything other than headshot.

You can of course change whom you are shooting. Consider this anti-team attack plugin for cs:
PHP Code:
#include <amxmodx>
#include <fakemeta>
#include <cstrike>

public plugin_init()
{
    
register_plugin("Traceline Tutorial - No TA""1.0""Nomexous")
    
    
register_forward(FM_TraceLine"traceline_forward")
}

public 
traceline_forward(Float:start[3], Float:end[3], conditionsidtrace)
{
    static 
hit
    
    hit 
get_tr2(traceTR_pHit)
    
    
// Variable id is shooter's index.
    
if (cs_get_user_team(id) == cs_get_user_team(hit))
    {
        
// Redirect onto himself!
        
set_tr2(traceTR_pHitid)
    }
    
    return 
FMRES_IGNORED

Blood will still spray out of whomever is being shot, but the damage will be dealt to the shooter. People quickly lose the urge to team attack.

I've submitted a plugin (Shot Administration) that demonstrates the evil capabilities of manipulating traceline. Take a look; tracehull is also hooked (for knife damage).

Less Evil Uses of Traceline

We will now use tracelines to do our bidding outside the context of player aiming. Using engfunc(), we can call draw our own tracelines to see what we hit. Of course, we're not actually shooting and dealing damage, but with the information that the trace result gives us, it can be just as useful.

Lets find out if two origins are in line of sight:
PHP Code:
stock bool:is_in_line_of_sight(Float:origin1[3], Float:origin[2], bool:ignore_players true)
{
    new 
trace 0
    engfunc
(EngFunc_TraceLineorigin1origin2, (ignore_players IGNORE_MONSTERS DONT_IGNORE_MONSTERS), 0trace)
    
    new 
Float:fraction
    get_tr2
(traceTR_flFractionfraction)
    
    return (
fraction == 1.0) ? true false

This stock sees how far it went before it hit something. If TR_flFraction is 1.0, that means it went 100% of the way without hitting an obstacle. Therefore, the two origins are in line of sight. Alternatively, you can see what TR_pHit returns. If it returns -1, you hit nothing (meaning the two points are in line of sight). A return of 0 means you hit part of the map (you can assume it's a wall), and anything else would be the index of an entity in the way. Be careful; it might not be a player. It could possibly be a door or a func_wall.

Now, let's make use of the TR_vecPlaneNormal part of our trace result. There are multiple things this is useful for. Here, I'll outline how to draw a laser normal to a surface.

When a trace hits something,
TR_vecPlaneNormal will have a unit vector (1 unit in length) stored to it, pointing perpendicularly away from surface at the spot it hit. To get the endpoint of the laser, we merely need to extend the length of the normal vector and add it to the origin of where we want the laser to start from. Here is a sample:
PHP Code:
#include <amxmodx>
#include <fakemeta>

#define PLUGIN "Draw Normal Laser Example"
#define VERSION "1.0"
#define AUTHOR "Nomexous"

new beampoint

public plugin_precache()
{
    
// Needed to show the laser.
    
beampoint precache_model("sprites/laserbeam.spr")
}

public 
plugin_init()
{
    
register_plugin(PLUGINVERSIONAUTHOR)
    
    
// I included the entire plugin because in order to draw the laser, you need to precache a sprite. Incorporate
    // these elements into your own plugin.
    
    // The shoot_laser() will (if the entity is on the floor) fire a laser from the entity, normal to the surface it's resting on.
}

public 
shoot_laser(ent)
{
    
// We get the origin of the entity.
    
new Float:origin[3]
    
pev(entpev_originorigin)
    
    
// We want to trace down to the floor, if it's there.
    
new Float:traceto[3]
    
traceto[0] = origin[0]
    
traceto[1] = origin[1]
    
traceto[2] = origin[2] - 10.0
    
    
new trace 0
    
// Draw the traceline. We're assuming the object is resting on the floor.
    
engfunc(EngFunc_TraceLineorigintracetoIGNORE_MONSTERSenttrace)
    
    new 
Float:fraction
    get_tr2
(traceTR_flFractionfraction)
    
// If we didn't hit anything, then we won't get a valid TR_vecPlaneNormal.
    
if (fraction == 1.0) return
    
    new 
Float:normal[3]
    
get_tr2(traceTR_vecPlaneNormalnormal)
    
// We'll multiply the the normal vector by a scalar to make it longer.
    
normal[0] *= 400.0 // Mathematically, we multiplied the length of the vector by 400*(3)^(1/2),
    
normal[1] *= 400.0 // or, in words, four hundred times root three.
    
normal[2] *= 400.0
    
    
// To get the endpoint, we add the normal vector and the origin.
    
new Float:endpoint[3]
    
endpoint[0] = origin[0] + normal[0]
    
endpoint[1] = origin[1] + normal[1]
    
endpoint[2] = origin[2] + normal[2]
    
    
// Finally, we draw from the laser!
    
draw_laser(originendpoint100// Make it stay for 10 seconds. Not a typo; staytime is in 10ths of a second.
}

public 
draw_laser(Float:start[3], Float:end[3], staytime)
{                    
    
message_begin(MSG_ALLSVC_TEMPENTITY)
    
write_byte(TE_BEAMPOINTS)
    
engfunc(EngFunc_WriteCoordstart[0])
    
engfunc(EngFunc_WriteCoordstart[1])
    
engfunc(EngFunc_WriteCoordstart[2])
    
engfunc(EngFunc_WriteCoordend[0])
    
engfunc(EngFunc_WriteCoordend[1])
    
engfunc(EngFunc_WriteCoordend[2])
    
write_short(beampoint)
    
write_byte(0)
    
write_byte(0)
    
write_byte(staytime// In tenths of a second.
    
write_byte(10)
    
write_byte(1)
    
write_byte(255// Red
    
write_byte(0// Green
    
write_byte(0// Blue
    
write_byte(127)
    
write_byte(1)
    
message_end()


Well, that's all I have for now. Hopefully, you've learned more about traceline and can take advantage of its features!

Last edited by Nomexous; 01-30-2008 at 16:28.
Nomexous is offline
Zenith77
Veteran Member
Join Date: Aug 2005
Old 01-23-2008 , 07:52   Re: Traceline Tutorial (Fakemeta)
Reply With Quote #2

Good tutorial. However, it should be noted in your line of sight example, that the two ents do NOT have to be facing each other for the stock to return true, they could in fact be facing the opposite way. Use, is_in_viewcone() for this functionaility. Other than that though, good job .
__________________
Quote:
Originally Posted by phorelyph View Post
your retatred
Zenith77 is offline
Arkshine
AMX Mod X Plugin Approver
Join Date: Oct 2005
Old 01-23-2008 , 11:33   Re: Traceline Tutorial (Fakemeta)
Reply With Quote #3

To alter a trace result, should not FM_TraceLine be registered in 'post' ? ( I has tested and you need to 'post-register' if you want your new value is taken )
__________________

Last edited by Arkshine; 01-23-2008 at 12:00.
Arkshine is offline
Nomexous
Member
Join Date: Oct 2007
Old 01-23-2008 , 12:04   Re: Traceline Tutorial (Fakemeta)
Reply With Quote #4

Quote:
Originally Posted by Zenith77 View Post
Good tutorial. However, it should be noted in your line of sight example, that the two ents do NOT have to be facing each other for the stock to return true, they could in fact be facing the opposite way. Use, is_in_viewcone() for this functionaility. Other than that though, good job .
The first two arguments of the stock are float origins, not entity indexes. So it's looking for line of sight between two points in space, not between two players. Of course, you're correct; if the two origins you provide happen to be the origins of two players, the stock will return the same value regardless of whether or not they are facing each other. But you won't always be using the stock on two players. I, for example, used it to find line of sight between a player and a flashbang.
Quote:
Originally Posted by arkshine View Post
To alter a trace value, should not FM_TraceLine be registered in 'post' ?
In all the times I've ever used traceline, I've never registered the forward as post. It works both ways, unlike tracehull, which needs to be registered as post. I've puzzled over it, but I can't come up with a good answer.
Nomexous is offline
Arkshine
AMX Mod X Plugin Approver
Join Date: Oct 2005
Old 01-23-2008 , 13:10   Re: Traceline Tutorial (Fakemeta)
Reply With Quote #5

Quote:
In all the times I've ever used traceline, I've never registered the forward as post. It works both ways, unlike tracehull, which needs to be registered as post. I've puzzled over it, but I can't come up with a good answer.
I'm talking about ( I have not expressed correctly ) only when you want to stop the trace result. ( e.g: allow damage only on head );

You can do that with : set_tr2( trace, TR_flFraction, 1.0 ); , but you need to supercede and it will work only if you have registered the forward as post.
__________________

Last edited by Arkshine; 01-23-2008 at 13:24.
Arkshine is offline
Orangutanz
Veteran Member
Join Date: Apr 2006
Old 01-23-2008 , 13:16   Re: Traceline Tutorial (Fakemeta)
Reply With Quote #6

Quote:
Originally Posted by arkshine View Post
To alter a trace result, should not FM_TraceLine be registered in 'post' ? ( I has tested and you need to 'post-register' if you want your new value is taken )
To be honest I think there is possibly an underlying problem with MetaMod regarding this since Post functionality doesn't really exist. It's essentially just executes before normal TraceLine function exits (I assume) but retains original values captured.

Back in the days of AMX Mod to correct a problem with TraceLine in Fun module another call to TraceLine had to be called, it might of been a API issue what Valve got round to fixing but I very much doubt it.

It's kind of the same story with removing hostages etc some days it works, other days it'll just crash HLDS with the same code which is very bizarre.
__________________
|<-- Retired from everything Small/Pawn related -->|
You know when you've been Rango'd
Orangutanz is offline
Nomexous
Member
Join Date: Oct 2007
Old 01-23-2008 , 13:34   Re: Traceline Tutorial (Fakemeta)
Reply With Quote #7

Added a link to Shot Administration, which demonstrates traceline and tracehull manipulation.
Nomexous is offline
[kirk]./musick`
Senior Member
Join Date: Jun 2007
Location: Tampa, Florida
Old 01-24-2008 , 19:26   Re: Traceline Tutorial (Fakemeta)
Reply With Quote #8

Very nice tutorial, I really liked it!
__________________
[kirk]./musick` is offline
Send a message via AIM to [kirk]./musick`
SAMURAI16
BANNED
Join Date: Sep 2006
Old 01-27-2008 , 00:37   Re: Traceline Tutorial (Fakemeta)
Reply With Quote #9

tell me what is wrong here:
Code:
public FORWARD_TRACELINE(Float:start[3], Float:end[3], conditions, id,trace) {     if(!is_user_alive(id))         return FMRES_IGNORED;             new ent = get_tr2(trace,TR_pHit);         if(!pev_valid(ent))         return FMRES_IGNORED;         static classname[31];     pev(ent,pev_classname,classname,30);             if(!equal(classname,gLandMinesClassname))         return FMRES_IGNORED;                 static Float:hitHealth;     pev(ent,pev_health,hitHealth);             if(hitHealth <= 1.0)     {         client_print(id,print_chat,"DESTROYED !! WORKS");         client_print(id,print_chat,"DESTROYED !! WORKS");     }     return FMRES_IGNORED;

I have my own ent, with own life. I shoot on it and i can destroy it. But if you see, I added a message when I hook TraceLine; That message don't appear.
Have any idea why ?

What i'm trying to do ?
When i shoot on a ent, and damage > ent's hp, it's enough to appear a simple message.
SAMURAI16 is offline
Send a message via MSN to SAMURAI16
Nomexous
Member
Join Date: Oct 2007
Old 01-27-2008 , 17:59   Re: Traceline Tutorial (Fakemeta)
Reply With Quote #10

@SAMURAI16

Unfortunately, you're limited by how traceline works. Traceline will not hit non-visible objects. If the solid flag is SOLID_NOT or SOLID_TRIGGER, traceline will pass right through it. For example, a func_breakable changes from SOLID_BBOX to SOLID_NOT when it's destroyed. I'm guessing that since your landmine is destroyed when shot, this is the case.

@Everyone else

I have another use of traceline coming up: making weapons lie flat on sloped surfaces!
Nomexous is offline
Reply



Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT -4. The time now is 07:45.


Powered by vBulletin®
Copyright ©2000 - 2024, vBulletin Solutions, Inc.
Theme made by Freecode