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

[TUT] Semiclip


Post New Thread Reply   
 
Thread Tools Display Modes
Author Message
Hawk552
AMX Mod X Moderator
Join Date: Aug 2005
Old 08-17-2007 , 01:24   [TUT] Semiclip
Reply With Quote #1

Before we begin, I'd just like to state that this tutorial is intended for intermediate to expert level scripters. You should be able to read all the code I've put here and almost immediately understand what should happen. Also, this tutorial is a bit of a detour from my previous ones; it's more of a discussion than a point by point process. I wouldn't be surprised if some of the code doesn't work (feel free to let me know, I'm always willing to fix it up).

I'm putting this up because there's been many requests for semiclip plugins lately. A lot of people seem to be stuck in my step 1 or 2 (as you'll see as you read further). Hopefully this will help get them on the right track.

For those of you that don't know, semiclip is exactly that: half-way between noclip and clipping. You can pass through other entities, but not through walls, nor can you fly.

I've had to try many different methods of semiclip and I've found that none at all are pretty. I have yet to find one be-all-end-all, but hopefully this will bring someone closer to making one. To start off, I'm going to show you the failed ones:

The Scripter's Initial Instinct - Failed Attempts

I first attacked this using simple stuff. The first (or one of the first) things that should come to your mind on how to solve this is using something like register_touch. Using this forward, we can pick up when 2 entities collide and either let the engine do its work or supersede it. Here's an example of how I tried this:

Code:
#include <amxmodx> #include <engine> public plugin_init() {     register_plugin("Semiclip Test 1","1.0","Hawk552")         register_touch("player","player","TouchPlayer") } public TouchPlayer(/*Ptr,Ptd*/)     return PLUGIN_HANDLED

This looks pretty nice and easy (of course you'd have to add more like cvar checks and such), but there's one catch: it really doesn't do what you want it to. Because of client prediction, the player gets stopped first and then passes through. If you're using this on the ground it's really not a big deal (although kind of annoying), but when you're using it for something like surfing (like I am), it becomes a big problem.

You can also use fakemeta in a similar way. I'm too lazy to type it out, but you can register FM_Touch, then check whether the touched and toucher are alive (is_user_alive).

The next method I went onto was constantly setting a player to unsolid. Again, this sounds very nice as it eliminates the problem of clipping right before the touch is blocked, but again there is a catch. The script first, of course:

Code:
#include <amxmodx> #include <engine> public plugin_init()     register_plugin("Semiclip Test 2","1.0","Hawk552") public client_PreThink(id)     entity_set_int(id,EV_INT_solid,SOLID_NOT)

Very nice and compact. In fact, you can probably not even use client_PreThink and instead do it in a forward like ResetHUD or something similar that isn't called all that often. But again for the hitch: the server crashes when you pass through any trigger_* entities.

Okay, not really a problem. We can run a check to scan around the player and see if they are within any distance of a trigger_* ent and then deactivate semiclip if they are. Sure, but what if 2 players are close to the trigger? They will collide, which is a problem. Then what about maps where the entire ground is trigger_teleports, such as some surf maps? This just gets too complicated. I suppose it has some potential, but I think there are really some better methods.

Potential Solutions

The final method I tried that failed was a direct scan around the player for other players. Essentially, you just scan around the player a certain distance, and if the search turns up another player, semiclip is activated. This method has the most potential, as we'll see in the final script. For now, this is what this would look like:

Code:
#include <amxmodx> #include <engine> public plugin_init()     register_plugin("Semiclip Test 3","1.0","Hawk552") public client_PreThink(id) {        new Ent,Float:Origin[3],On = SOLID_BBOX     entity_get_vector(id,EV_VEC_origin,Origin)     while((Ent = find_ent_in_sphere(Ent,Origin,100.0)) != 0)         if(is_user_alive(Ent))             On = SOLID_NOT         entity_set_int(id,EV_INT_solid,On) }

But we run into yet another problem here: the checking simply isn't fast enough. If we make the radius insanely large, we run into the risk of crashing the server if the user is close to a trigger_* entity. There's no way to solve this the way it is now, client_PreThink is simply too slow.

The Only Known Solution

If you can challenge the above statement, feel free to. I'd like to see a better solution personally, I could use it. But for now, this is the closest to perfect semiclip that I know of. A demonstration of it in use can be found in uSurf. Essentially, it's the last attempt except sped up using an entity thinking every 0.01 seconds. Very very resource intensive, but it gives the most accuracy and responsiveness. Here's the implementation from uSurf with uSurf specific stuff stripped out:

Code:
#include <amxmodx> #include <fakemeta> new g_SemiclipThinkerClassname[] = "env_semiclip" new g_SemiclipThinker public plugin_init() {     register_plugin("Semiclip Test 4","1.0","Hawk552")         register_forward(FM_Think,"ForwardThink")         new Ent = engfunc(EngFunc_CreateNamedEntity,engfunc(EngFunc_AllocString,"info_target"))     if(!Ent)         return         set_pev(Ent,pev_classname,g_SemiclipThinkerClassname)     new Float:Time     global_get(glb_time,Time)     set_pev(Ent,pev_nextthink,Time + 0.01)     dllfunc(DLLFunc_Spawn,Ent)         g_SemiclipThinker = Ent } public ForwardThink(Ent) {     if(Ent != g_SemiclipThinker)         return         static Players[32],Playersnum,Player     get_players(Players,Playersnum)         for(new Count;Count < Playersnum;Count++)     {                    Player = Players[Count]         if(!is_user_alive(Player))             continue                 set_pev(Player,pev_solid,IsColliding(Player) ? SOLID_NOT : SOLID_BBOX)     }         new Float:Time     global_get(glb_time,Time)     set_pev(Ent,pev_nextthink,Time + 0.01) } IsColliding(id) {        new Ent,Float:Origin[3]     pev(id,pev_origin,Origin)             while((Ent = engfunc(EngFunc_FindEntityInSphere,Ent,Origin,36.0)) != 0)         if(Ent > 0 && Ent <= 32 && is_user_alive(Ent) && Ent != id)             return true         return false }

Is this level necessary for most uses? No, most people just want to be able to pass through others. For that, the first method may even work. Feel free to use whatever you want, there's really no standard as they all suck.

As always, if you have any questions, comments or whatever feel free to post. As aforementioned, I invite anyone to challenge anything said here. Hope this helps.
__________________

Last edited by Hawk552; 08-17-2007 at 13:46. Reason: Fixed problems
Hawk552 is offline
Send a message via AIM to Hawk552
Fredd
Veteran Member
Join Date: Jul 2007
Old 08-17-2007 , 04:56   Re: [TUT] Semiclip
Reply With Quote #2

you forget to register a think\ register FM_think forward.
other than that, this is really usefull in so many ways thx man gj
__________________
Need a private coder? AMXX, SourceMOD, MMS? PM me!
Fredd is offline
Hawk552
AMX Mod X Moderator
Join Date: Aug 2005
Old 08-17-2007 , 13:46   Re: [TUT] Semiclip
Reply With Quote #3

Quote:
Originally Posted by Fredd View Post
you forget to register a think\ register FM_think forward.
other than that, this is really usefull in so many ways thx man gj
You're right, fixed.
__________________
Hawk552 is offline
Send a message via AIM to Hawk552
Zenith77
Veteran Member
Join Date: Aug 2005
Old 08-18-2007 , 00:56   Re: [TUT] Semiclip
Reply With Quote #4

Long time no see.
__________________
Quote:
Originally Posted by phorelyph View Post
your retatred
Zenith77 is offline
Hawk552
AMX Mod X Moderator
Join Date: Aug 2005
Old 08-18-2007 , 12:18   Re: [TUT] Semiclip
Reply With Quote #5

Quote:
Originally Posted by Zenith77 View Post
Long time no see.
Yeah, I was busy.
__________________
Hawk552 is offline
Send a message via AIM to Hawk552
Fredd
Veteran Member
Join Date: Jul 2007
Old 08-18-2007 , 20:00   Re: [TUT] Semiclip
Reply With Quote #6

I like busy. anyways welcome back
__________________
Need a private coder? AMXX, SourceMOD, MMS? PM me!
Fredd is offline
stupok
Veteran Member
Join Date: Feb 2006
Old 08-19-2007 , 14:43   Re: [TUT] Semiclip
Reply With Quote #7

I tried to optimize it, tell me what you think:

Code:
#include <amxmodx> #include <fakemeta> new g_SemiclipThinkerClassname[] = "env_semiclip" new g_SemiclipThinker #define MAX_PLAYERS 32 new g_maxplayers new bool:g_restart_attempt[MAX_PLAYERS + 1] new bool:g_is_user_alive[MAX_PLAYERS + 1] public plugin_init() {     register_plugin("Semiclip Test 4","1.0","Hawk552")         register_forward(FM_Think, "forward_Think")         //VEN's method to capture player spawn     register_event("ResetHUD", "event_hud_reset", "be")     register_clcmd("fullupdate", "clcmd_fullupdate")     register_event("TextMsg", "event_restart_attempt", "a", "2=#Game_will_restart_in")     register_event("DeathMsg", "event_death", "a")         g_maxplayers = global_get(glb_maxClients)         new Ent = engfunc(EngFunc_CreateNamedEntity,engfunc(EngFunc_AllocString,"info_target"))     if(!pev_valid(Ent))         return         set_pev(Ent,pev_classname,g_SemiclipThinkerClassname)     new Float:Time     global_get(glb_time,Time)     set_pev(Ent,pev_nextthink,Time + 0.01)     dllfunc(DLLFunc_Spawn,Ent)         g_SemiclipThinker = Ent } public forward_Think(Ent) {     if(Ent != g_SemiclipThinker)         return         static i,Float:Time         for(i=0;i<=g_maxplayers;i++)     {         if(g_is_user_alive[i])         {             set_pev(i,pev_solid,IsColliding(i) ? SOLID_NOT : SOLID_BBOX)         }     }         global_get(glb_time,Time)     set_pev(Ent,pev_nextthink,Time + 0.01) } stock IsColliding(id) {     if(pev(id,pev_flags) & FL_ONGROUND || pev(id,pev_button) & IN_JUMP)         return false         static Ent,Float:Origin[3]         pev(id,pev_origin,Origin)         while((Ent = engfunc(EngFunc_FindEntityInSphere,Ent,Origin,36.0)) != 0)         if(Ent > 0 && Ent <= g_maxplayers && g_is_user_alive[Ent])             return true         return false } public clcmd_fullupdate() {     return PLUGIN_HANDLED_MAIN } public event_restart_attempt() {     new players[32], num     get_players(players, num, "a")     for (new i; i < num; ++i)         g_restart_attempt[players[i]] = true } public event_hud_reset(id) {     if (g_restart_attempt[id])     {         g_restart_attempt[id] = false         return     }     event_player_spawn(id) } // this function is called on player spawn public event_player_spawn(id) {     g_is_user_alive[id] = true } public event_death(id) {     g_is_user_alive[read_data(2)] = false }
__________________
stupok is offline
Old 08-19-2007, 16:35
Hawk552
This message has been deleted by Hawk552.
Old 08-19-2007, 17:13
stupok
This message has been deleted by Hawk552.
Old 08-19-2007, 19:20
Hawk552
This message has been deleted by Hawk552.
stupok
Veteran Member
Join Date: Feb 2006
Old 08-19-2007 , 20:33   Re: [TUT] Semiclip
Reply With Quote #8

Thanks, Hawk. If I ever need some help with coding plugins, I'll know where to go.
__________________
stupok is offline
Fredd
Veteran Member
Join Date: Jul 2007
Old 08-19-2007 , 21:33   Re: [TUT] Semiclip
Reply With Quote #9

this is a good sign, it means he will be around for a while.
__________________
Need a private coder? AMXX, SourceMOD, MMS? PM me!
Fredd is offline
Orangutanz
Veteran Member
Join Date: Apr 2006
Old 08-23-2007 , 14:17   Re: [TUT] Semiclip
Reply With Quote #10

Optimisation (IsColliding(id)):
new Ent,Float:Origin[3]

Should be:
static Ent,Float:Origin[3]

Reason being well it can be potentially called as often as ForwardThink(Ent).


Optimisation (public ForwardThink(Ent)):
new Float:Time

Should be:
static Float:Time

Same reason as the other.

IMO and I think it is covered on the WIKI, all fast ticking function variables should be declared static.


Cheers,

Orang
__________________
|<-- Retired from everything Small/Pawn related -->|
You know when you've been Rango'd

Last edited by Orangutanz; 08-23-2007 at 14:20.
Orangutanz is offline
Reply


Thread Tools
Display Modes

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 03:40.


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