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: Select all
#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_HANDLEDYou 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: Select all
#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)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: Select all
#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)
}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: Select all
#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
}
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.
