Finding an Object within a given Radius

From Mod Wiki

(Difference between revisions)
Jump to: navigation, search
Revision as of 03:36, 23 April 2010 (edit)
Alundaio (Talk | contribs)
(Finding the NEAREST object to an object)
← Previous diff
Current revision (08:30, 26 April 2010) (edit) (undo)
Alundaio (Talk | contribs)
(Removing all content from page)
 
(13 intermediate revisions not shown.)
Line 1: Line 1:
-= Finding an Object Within the Radius of Another Object = 
- 
-== Introduction ==  
- 
-First, I would like to talk about the various methods on how to do this. Then I will present you my solution in which I think is more efficient as opposed to these other methods. 
-To my knowledge there is no current function that will do this easily and efficient. If I am wrong in thinking such then feel free to present me the evidence that my method 
-is not efficient in comparison. Herein are examples, most of which I tested, in which I will show you how to find an object within a radius relative to another object (db.actor in my examples). 
- 
-== Update function in bind_monster and xr_motivator == 
- 
-The usual method is to put a "distance_to_sqr" or "distance_to" check here for the object. For instance, if you wanted to play 
-a sound when a certain monster community was a certain distance away from the player, you would put something like this: 
-<pre> 
- if (self.object:alive() and character_community(self.object) == "bloodsucker") then 
- if (self.object:position():distance_to(db.actor:position()) < 10 then 
- -- play sound 
- end 
- end 
-</pre> 
-Of course if you use that as is, it's going to play continuously when the bloodsucker was less then 10 meters away from the player. You would have to add in a delay or a heartbeat like this. 
-<pre>  
- -- Add some local var outside the scope of the update function 
- local search_flag = false 
- local update_time = nil 
-  
- function generic_object_binder:update(delta) 
- 
- ... 
-  
- if ( search_flag == false ) then 
- if (self.object:alive() and character_community(self.object) == "bloodsucker") then 
- if ( self.object:position():distance_to(db.actor:position()) < 10 ) then 
- -- play sound 
- search_flag = true 
- update_time = time_global() + 60 -- set next search to be in 1 second 
- end 
- end 
- else 
- -- Check if current time matches next update time 
- if (update_time ~= nil and time_global() <= update_time) then 
- search_flag = false 
- update_time = nil 
- end 
- end 
-</pre> 
-The sound will now only play every second when the bloodsucker is less then 10 meters away. Also a benefit is that it will only do the distance calculation every second, making it a little more efficient. But,  
-there is also several things wrong in doing something like this. First, every single generic (or Monster) server object is going to run this condition. It's cut down some by checking for a certain community and if the client  
-object is alive. Even still this is a very inefficient approach. 
- 
- 
-== Searching db.storage for the object == 
- 
- 
-Another approach is to run a search on the db.storage table for the object you want to check for. Usually used if you for some reason don't want it in the other binder scripts. It's usually 
-put in the bind_stalker script. 
-<pre> 
- -- bind_stalker.script 
- -- Add some local vars outside the scope of actor update function 
- local search_flag = false 
- local update_time = nil 
-  
- function actor_binder:update(delta) 
-  
- ... 
-  
- if ( search_flag == false ) then 
-  
- for key, value in pairs (db.storage) do 
- obj = db.storage[key].object 
-  
- if (obj and IsMonster(obj) and obj:alive() and character_community(obj) == "bloodsucker") then 
- if ( obj:position():distance_to(db.actor:position()) < 10 ) then 
- -- play sound 
- search_flag = true 
- update_time = time_global() + 60 -- set next search to be in 1 second 
- break -- Break out of loop on first match 
- end 
- end 
- end 
- else 
- -- Check if current time matches next update time 
- if (update_time ~= nil and time_global() <= update_time) then 
- search_flag = false 
- update_time = nil 
- end 
-  
- end 
-  
-</pre>  
-This looks similar to the previous method except it does things much differently. First, it will run through the entire '''db.storage''' table and run a distance calculation on each ''online'' object 
-that matches the condition. In my opinion this is more efficient then the previous for obvious reason. It doesn't run the distance calculation on every single instance of the generic object binder  
-(Monster server object) on each update. How can we make this more efficient? 
- 
- 
-== More efficient approach == 
- 
- 
-The last method I showed you that you can grab the online object from the '''db.storage''' table and match it to some conditions to get the result you want. Now I'll show you a trick on making searching 
-the table more efficient. 
- 
-Go into the '''db.script''' and add these below: 
-<pre> 
- monster_by_name = {} 
- stalker_by_name = {} 
- 
- function add_monster( obj ) 
- monster_by_name[obj.object:name()] = obj 
- end 
- 
- function del_monster( obj ) 
- monster_by_name[obj.object:name()] = nil 
- end 
- 
- function add_stalker( obj ) 
- stalker_by_name[obj.object:name()] = obj 
- end 
- 
- function del_stalker( obj ) 
- stalker_by_name[obj.object:name()] = nil 
- end 
-</pre>  
-If you took the time to figure out what you just put in the db.script you'll realize you are defining two new global tables and two new functions to push an object's name into that table. 
-Now go into '''xr_motivator.script'''. 
- 
- 
-Look for: 
-<pre> 
- function motivator_binder:net_spawn(sobject) 
-</pre>  
-And find '''db.add_obj(self.object)''' and put this underneath it: 
-<pre> 
- db.add_obj(self.object) 
- db.add_stalker(self) -- Added db.add_stalker(self) 
-</pre> 
-Look for: 
-<pre> 
- function motivator_binder:net_destroy() 
-</pre>  
-And find '''db.del_obj(self.object)''' and put this underneath it: 
-<pre> 
- db.del_obj(self.object) 
- db.del_stalker(self) -- Added db.del_stalker(self) 
-  
-</pre>  
-Now go into '''bind_monster.script''' and find '''net_spawn()''' and '''net_destroy()''' adding '''db.add_monster(self)''' and '''db.del_monster(self)''' respectfully. 
- 
- 
-"What the hell are you having me do?" you may ask. Well everytime the server object for a stalker or monster goes ''online'' it will store it's self into a table. This will make iterating 
-through tables for a specific object type much more efficient. 
-<pre> 
- 
- -- bind_stalker.script 
- -- Add some local vars outside the scope of actor update function 
- local search_flag = false 
- local update_time = nil 
-  
- function actor_binder:update(delta) 
-  
- ... 
-  
- if ( search_flag == false ) then 
-  
- for key, value in pairs (db.monster_by_name) do 
- obj = value.object -- Value will hold the server object's name. so value.object will give us it's online client instance 
-  
- if (obj and obj:alive() and character_community(obj) == "bloodsucker") then 
- if ( obj:position():distance_to(db.actor:position()) < 10 ) then 
- -- play sound 
- search_flag = true 
- update_time = time_global() + 60 -- set next search to be in 1 second 
- break -- Break out of loop on first match 
- end 
- end 
- end 
- else 
- -- Check if current time matches next update time 
- if (update_time ~= nil and time_global() <= update_time) then 
- search_flag = false 
- update_time = nil 
- end 
-  
- end  
-</pre> 
-If you just screamed inside "It's the same thing", well...you're wrong! The table is dramatically smaller and now we don't have to check for '''IsStalker(obj)''' or '''IsMonster(obj)''' because we 
-are searching a specific table for what we are looking for. Let's examine further why this method is much more efficient and better then all the previous methods. 
- 
-1. The conditions and the distance calculations aren't being ran on every update for each and every generic object binder (monsters) or motivator binder (stalkers). 
- 
-2. Only online objects will be in the table. Only if the conditions match will the '''distance_to''' calculations be made. 
- 
-3. Finding the nearest object is only a few conditions away! That's right! 
- 
- 
-== Finding the NEAREST object to an object == 
- 
- 
-I mentioned that finding the nearest object is really easy this way. Here's how! 
-<pre> 
- -- bind_stalker.script 
- -- Add some local vars outside the scope of actor update function 
- local search_flag = false 
- local update_time = nil 
-  
- local FOUND_OBJECT = nil 
- local FOUND_OBJECT_DISTANCE = nil 
-  
- function actor_binder:update(delta) 
-  
- ... 
-  
- if ( search_flag == false ) then 
- local dist = nil 
- local lowest_dist = nil 
-  
- for key, value in pairs (db.monster_by_name) do 
- obj = value.object -- Value will hold the server object's name. so value.object will give us it's online client instance 
-  
- if ( obj and obj:alive() and character_community( obj ) == "bloodsucker" ) then 
- dist = obj:position():distance_to(self.myobject:position()) 
- 
- if (dist <= 10) then 
- if (lowest_dist == nil) then 
- lowest_dist = dist 
- end 
-  
- -- Keep only the lowest found distance 
- if (dist < lowest_dist) then 
- FOUND_OBJECT = obj 
- FOUND_OBJECT_DISTANCE = dist 
- lowest_dist = dist 
- dist = nil 
- end 
- end 
- end 
- end 
- lowest_dist = nil 
-  
- -- Do stuff after table has been iterated fully 
- -- FOUND_OBJECT hold's the object of the nearest object found. 
- -- FOUND_OBJECT_DISTANCE hold's that objects current distance away from actor. 
- search_flag = true 
- else 
- -- Check if current time matches next update time 
- if (update_time ~= nil and time_global() <= update_time) then 
- search_flag = false 
- update_time = nil 
- end 
-  
- end  
- 
-</pre> 
-As you can see only a few things were changed. We removed the break to allow the table to be fully iterated while keeping track of the lowest distance found, resulting in the nearest object. 
-You may also guess that this might possibly be less efficient then just searching for an object within a radius. But in my opinion it is ''still'' more efficient then running a distance check 
-on an update function on every single stalker or monster. Still, one must use this method sparringly in an update function. 
- 
-Now the rest is up to you. Keep in mind how often you want to check for an object within a certain radius. Using longer heartbeats will lessen the workload of each update. You can take a look at the other tables in the '''db.script''' to find another object type. Like anomalies for instance. Watch when using a table that stores the '''object:id()''' instead of the name. You'll have to use '''alife():object(value)''' to get the online client object. I also suggest turning this into a function and putting it in the _G.script if you are going to use it for multiple things. 
- 
- 
---[[User:Alundaio|Alundaio]] 05:29, 23 April 2010 (EEST) 

Current revision

Personal tools