PDA

View Full Version : [HOWTO] Finding a Random Origin (Scanning)


Hawk552
01-13-2007, 18:12
Before you begin, this tutorial assumes that you are an intermediate to advanced level scripter. Most things to do with any implementations in AMXX will not be explained, and it will assume that you understand everything except for the way to do this itself (wouldn't really be a tutorial, would it?).

What I'm basically going to be explaining is how to find a random origin that is empty and can be used to put something at, do something with, or otherwise use in some way.

The most important thing to do is establish a refence point. What are you trying to do? Are you trying to find an origin around a person? Are you trying to find a random place on the map to drop something? For the purpose of this, we will assume the former.

This function can be abstracted out quite easily:


FindEmptyLoc(id,Float:Origin[3],&Num,Float:Radius)
{
if(Num++ > 100)
return PLUGIN_CONTINUE

new Float:pOrigin[3]
pev(id,pev_origin,pOrigin)

for(new Count;Count < 2;Count++)
pOrigin[Count] += random_float(-Radius,Radius)

if(PointContents(pOrigin) != CONTENTS_EMPTY && PointContents(pOrigin) != CONTENTS_SKY)
return FindEmptyLoc(id,Origin,Num,Radius)

Origin = pOrigin

return PLUGIN_HANDLED
}


This will scan around the user with a radius of Radius units for an empty location. If it doesn't find any, it will return 0, otherwise it will return 1. Num is simply a dummy variable to track how many times it has scanned around the user. It's best to set this to 0.

If you want to be sure the user can see it, you can run a trace_line (or TraceLine) between the user and the result. I assume you know how to do this and will not draw it out.

Here's an example implementation:


public MyFunc(id)
{
new Float:Origin[3],Num
pev(id,pev_origin,Origin)

if(!FindEmptyLoc(id,Origin,Num,100.0))
return

new Ent = create_entity("info_target")
if(!Ent)
return

set_pev(Ent,pev_classname,"zomg")
set_pev(Ent,pev_model,"models/mymodel.mdl")
engfunc(EngFunc_SetOrigin,Ent,Origin)
}


This will find an empty origin around the player, spawn an ent, then set its model and origin to that empty location.

Anyway, this may seem pretty simple, but a few people have asked me about how to do it, so I thought I'd write this up.

As always, if you have any questions, comments, or whatever else you can think of, feel free to post.

Cheap_Suit
01-15-2007, 06:51
WOW! I needed his! Thnx

regalis
05-13-2007, 11:03
Nice Tutorial Hawk552 !!
I used this method in my new Plugin, but i have encountered that the Webcompiler have a problem with random_float()

It looks like the webcompiler's float.inc wasn't updated for 1.76d.
...
...
Try taking all instances of -Float (such as -Radius like you do in that function) and replace it with this:

(-1 * Variable)

See if that fixes it.
Now i have made this changes and it works flawless with the Webcompiler...


FindEmptyLoc(id,Float:Origin[3],&Num,Float:Radius)
{
if(Num++ > 100)
return PLUGIN_CONTINUE

new Float:pOrigin[3]
new Float:rnd;
pev(id,pev_origin,pOrigin)

for(new Count;Count < 2;Count++)
{
// This doesn't work with the Webcompiler...you have to do a workaround...
//pOrigin[Count] += random_float(-Radius,Radius)
rnd = random_float(0.0, Radius)
if(random(2))
pOrigin[Count] += rnd
else
pOrigin[Count] -= rnd
}
if(PointContents(pOrigin) != CONTENTS_EMPTY && PointContents(pOrigin) != CONTENTS_SKY)
return FindEmptyLoc(id,Origin,Num,Radius)

Origin = pOrigin

return PLUGIN_HANDLED
}

Maybe you can improve my changes!?
Because i don't really understand the (-1 * Variable) thingy...
If i change random_float(-Radius, Radius) to random_float((-1 * Radius), Radius) where is the difference!?..0o

greetz regalis

PS: keep up the good work! Your tutorials are very helpfull!!! :)

VEN
05-13-2007, 11:57
It's related to Float:operator-(Float:oper) stock function from float.inc issue that was fixed but they didn't updated webcompiler includes.

The difference is that -1 (or even -1.0) isn't pass to the operator because it's a constant value.

_Master_
05-14-2007, 05:13
Recursion... might be a bit costly if a valid point is not found in the first 2-3 attempts.

regalis
05-14-2007, 05:42
Recursion... might be a bit costly if a valid point is not found in the first 2-3 attempts.

Do you think a while loop would be more suitable?

Hawk552
05-14-2007, 06:44
Recursion... might be a bit costly if a valid point is not found in the first 2-3 attempts.
Agreed, I'll rewrite it later.

Zenith77
05-14-2007, 11:42
Do you think a while loop would be more suitable?

How would that help?

regalis
05-14-2007, 12:24
How would that help?

huh?
What helps whom?
The code is made recursive, and _master_ thought about the costs of this recursion...then i thought that maybe a while loop would be more suitable thats why i asked if that would be better...

_Master_
05-15-2007, 15:30
Do you think a while loop would be more suitable?
Any recursive algorithm has an iterative implementation. The thing is that if there's no valid point it will loop forever. This won't be the case.
It all depends on how Hawk552 decides to implement that algorithm. (might I add that this issue has been dealt with before and handled - but I guess that neither Backtracking, Greedy, partitioning nor dynamic methods would help :D)

regalis
05-16-2007, 03:02
FYI ;)

http://forums.alliedmods.net/images/statusicon/post_old.gif (http://forums.alliedmods.net/showthread.php?p=476924#post476924) Yesterday , 19:26 Re: Possible bug in webcompiler!?

This should now be fixed. Thanks!
__________________
BAN

Fredd
08-17-2007, 05:06
Hawk@ you might want to add the way to find a random origin on the map like in your zombie mod ^^

Hawk552
08-17-2007, 13:45
Hawk@ you might want to add the way to find a random origin on the map like in your zombie mod ^^

Yeah I will.

X-Script
09-08-2007, 13:34
Hawk552, could you post an example how to spawn a model on a users dead body?

EDIT: Wouldn't it be as simple as doing if ( !is_user_alive(id) )

Hawk552
09-08-2007, 13:58
... umm... no. But I have done this before, so I'll do it. Expect to see it up some time today.

X-Script
09-08-2007, 14:25
Thank you, i've been wondering how to do it for awhile now and I need it for my plugin.

Hawk552
09-08-2007, 17:19
Posted. Didn't turn out as well as I hoped, though.

drekes
05-17-2010, 02:36
Thanks man. This will come in handy.

drekes
05-17-2010, 12:45
I tried this and i have 2 problems:
1. The entity spawns on a unreachable place.
2. i tried to spawn 10 entity's by setting a task 10 times, but there's only one in the map.

Exolent[jNr]
05-17-2010, 13:06
Do you want a random origin near the player, or a random origin in the map?

Hawk552
05-17-2010, 13:15
I have some code for both which is much better than this, but I don't have access to it here. I'll post it when I get home.

drekes
05-17-2010, 13:19
@ Exolent: I want a random origin in the map, but it must be reachable for a player. It's for the coins thing from earlier today.

@ hawk552: Thanks, i have found a thirth problem to. i spawn 2 things, a box and a coin. But they spawn in eachother. Maybe your new code fixes that to?

Thanks anyway for replying so fast.

Exolent[jNr]
05-17-2010, 13:41
You can take code from here (http://forums.alliedmods.net/showthread.php?t=75882) for random reachable origins throughout a map.

Hawk552
05-17-2010, 13:52
;1183361']You can take code from here (http://forums.alliedmods.net/showthread.php?t=75882) for random reachable origins throughout a map.

That's kind of slow. I have something that runs faster but isn't as accurate.

Exolent[jNr]
05-17-2010, 13:56
That's kind of slow. I have something that runs faster but isn't as accurate.

Well, it's purpose is for a one-time use, then save the results.
It can be modified to find more than just 32 spawn points so that they can be generated for say 100 different locations then saved.
When you need one for the player, just find the closest 5 and choose a random one.
That should be enough.

drekes
05-17-2010, 14:10
As long as i can place 20 entity's and a player can reach them, i'm happy. I looked at GHW's code, but i find it very complicated.

Hawk552
05-17-2010, 14:14
If you don't mind the one-time getting and saving, then you should use his. I gather that mine is no where near as accurate, though I could be wrong.

drekes
05-17-2010, 14:15
Could you define accurate a bit more?
You mean some will not be reachable?

Hawk552
05-17-2010, 14:17
Could you define accurate a bit more?
You mean some will not be reachable?
My code just picks random spots and checks if the point itself is clear of random shit using point contents and hull tracing. It isn't necessarily reachable. The only good way to determine whether or not something is reachable is to save player origins once in a while, but that disables it for the first run unless you temporarily substitute some other algorithm while it's getting good coordinates.

drekes
05-17-2010, 14:22
Sorry, but my english is not that good, i don't understand this part

but that disables it for the first run unless you temporarily substitute some other algorithm while it's getting good coordinates.
The ent should be reachable at all times. So i guess i should use exolents link?

EDIT: i looked at Exolents link again, but i don't understand the code. I'm having big problems with the spawn things. I also checked CSDM code. But that's even more complicated that GHW_Chronic's one.

Hawk552
05-17-2010, 14:38
Sorry, but my english is not that good, i don't understand this part
The ent should be reachable at all times. So i guess i should use exolents link?

I don't think Chronic's code will guarantee that it's always reachable. If it's up on top of a box or behind a wall or something (like on de_dust, kind of but not really near the T spawn) then it can't really do anything about that.

Exolent[jNr]
05-17-2010, 14:41
I created some code as an example for you.

#include < amxmodx >
#include < amxmisc >
#include < GHW_spawnlist_gen >

new bool:loaded_spawns
new save_file[ 128 ]

public plugin_init( )
{
get_datadir( save_file, charsmax( save_file ) )
new len = add( save_file, charsmax( save_file ), "/some_spawns" )

if( !dir_exists( save_file ) )
{
mkdir( save_file )
}

save_file[ len++ ] = '/'
get_mapname( save_file[ len ], charsmax( save_file ) - len )
add( save_file, charsmax( save_file ), ".txt" )

if( !( loaded_spawns = load_spawns( ) ) )
{
genspawnlist( 1, MAX_ORIGINS )
set_task( TIMEOUT, "save_spawns" )
}
}

bool:load_spawns( )
{
num_origins = 0

new f = fopen( save_file, "rt" )

if( !f )
{
return false
}

new data[ 64 ], i, char_loc

while( !feof( f ) && num_origins < MAX_ORIGINS )
{
fgets( f, data, charsmax( data ) )
trim( data )

if( data[ 0 ] )
{
i = char_loc = 0

do
{
origins[ num_origins ][ i ] = str_to_float( data[ char_loc ] )
}
while( ++i < 3 && ( char_loc += ( contain( data[ char_loc ], "," ) + 1 ) ) != -1 )

num_origins++
}
}

fclose( f )

return true
}

public save_spawns( )
{
loaded_spawns = num_origins > 0

new f = fopen( save_file, "wt" )

if( !f )
{
return
}

for( new i = 0; i < num_origins; i++ )
{
fprintf( f, "%f,%f,%f^n", origins[ i ][ 0 ], origins[ i ][ 1 ], origins[ i ][ 2 ] )
}

fclose( f )
}

spawnCoin( id )
{
if( !loaded_spawns )
{
return
}

new spawn_index = random( num_origins )

// create entity at origins[ spawn_index ]
}

drekes
05-17-2010, 14:45
Thanks Exolent, i think i understand it now.
I'll try it and let you know if i figured it out.

I made it. and it makes the directory in the data folder, but create the file in data folder instead of data/mario_spawns and file in named mario_spawns.txt

I did this:

// Random Item spawns
new len = get_datadir(save_file, charsmax(save_file))
len += add(save_file, charsmax(save_file), "/mario_spawns")

if(!dir_exists(save_file))
{
mkdir(save_file)
}

save_file[len++] = '/'

get_mapname(save_file[len], charsmax(save_file) - len)
add(save_file, charsmax(save_file), ".txt")

if(!(loaded_spawns = load_spawns()))
{
genspawnlist(1, MAX_ORIGINS)
set_task(TIMEOUT, "save_spawns")
}
I tried with this to, same effect.

new datadir[128], mapname[33], filename[33]
get_datadir(datadir, 127)

get_mapname(mapname, 32)

formatex(filename, 33, "%s/mario_spawn/%s", datadir, mapname)
add(filename, charsmax(filename), ".txt")

if(!(loaded_spawns = load_spawns()))
{
genspawnlist(1, MAX_ORIGINS)
set_task(TIMEOUT, "save_spawns")
}
@Hawk552:
Sorry, I looked over your post. Which will guarantee the most reachable? Yours or Chronics?
I'm having troubles with the directory, can't seem to figure it out. :cry:

EDIT: I fixed it, it works now, but as Hawk552 said: sometimes they are unreachable. Do you know how i can add like csdm spawnmaker from Chronic, the testing part to see if everything is rigth.

drekes
05-19-2010, 00:29
i have a new question:

Is there a way to determine how many spawns there are, and then do something like this:

max_item_spawns // the amount of spawns

set_task(1.0, "spawn_coin", _, _, _, "a", max_item_spawns / 2)
set_task(1.0, "spawn_box", _, _, _, "a", max_item_spawns / 2)

Exolent[jNr]
05-19-2010, 00:34
i have a new question:

Is there a way to determine how many spawns there are, and then do something like this:

max_item_spawns // the amount of spawns

set_task(1.0, "spawn_coin", _, _, _, "a", max_item_spawns / 2)
set_task(1.0, "spawn_box", _, _, _, "a", max_item_spawns / 2)


num_origins is the total number of origins..

drekes
05-19-2010, 01:06
Thanks Exolent