Category talk:Articles

From Mod Wiki

Revision as of 03:14, 23 April 2010 by Alundaio (Talk | contribs)
(diff) ←Older revision | Current revision (diff) | Newer revision→ (diff)
Jump to: navigation, search

Contents

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 and 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:

   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

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.

	
	-- 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

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.

	-- 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
	

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:

	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

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:

	function motivator_binder:net_spawn(sobject)

And find db.add_obj(self.object) and put this underneath it:

	db.add_obj(self.object)
	db.add_stalker(self)    -- Added db.add_stalker(self)

Look for:

	function motivator_binder:net_destroy()

And find db.del_obj(self.object) and put this underneath it:

	db.del_obj(self.object)
	db.del_stalker(self)	-- Added db.del_stalker(self)
	

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.


	-- 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	

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!

	-- 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.
	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	

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.


--Alundaio 05:14, 23 April 2010 (EEST)

Personal tools