Best way to handle portals?
#1
So I'm currently writing a portals plugin. Right now, it's only going to be for BungeeCord, but eventually I might make it for other things (like multiworld, in world, etc). I've already figure out how I can define areas (either using a worldedit API call or using a special stick to get the two corners), so that's not the problem. The thing I want input on is how best to handle checking if a player is in a portal. There's a number of methods, but it would have to use the onPlayerMoved(ing? one of those) hook, correct? As such, I want to do as minimal a processing as possible. I've come up with a few simple ideas, but each one has downsides. There are also a few other options I've come up with (such as portals being made of specific blocks so I can check for blocktype or air/portal block beneath the player and return false if they don't match), but I'd rather not use those methods. So, here are my ideas:

Method 1:
Nested for's every ... no.

Method 2:
Store all portals in, say, g_Portals, in the format
g_Portals = {
  name1: {
    xmin = 0
    xman = 1
    ymin = 10
    ymax = 100
    zmin = 10
    zmax = 100
    information = { -- information goes here
    }
  },
  ....
}

function checkPortal(x,y,z)
  for k,v in pairs(g_Portals) do
    if v.xmin <= x and v.xmax >= x and ... and v.zmax <= z then
      return v.information
    end
  end
  return false
end

Pro: Handles a small number of any size portals extremely well
Con: A large number of portals/players could cause serious server lag.

Method 3:
On startup, the plugin takes an input table of the form from method 2 (probably actually sqlite information, unless I get lazy again and just use file storage) and produces a table of all coordinates in the form
g_PortalBlocks[x_value][y_value][z_value] = "portal_name"
and then references another table (say, g_Portals) like g_Portals["portal_name"] = {..information here..} so that the code would like like this
function checkPortal(x,y,z)
  return g_Portals[g_PortalBlocks[x][y][z]]
end
Then you could just use an if to check if the return is nil, and if not, use the information to teleport. This plugin would use the integer location type for this, so that decimals don't mess everything up.

Pros: Incredibly fast, no matter how many people/portals there are.
Cons: A large number of portals and/or large portals could cause massive memory usage.

Method 4:
Using a cCuboid constructed at startup (likely as an element of the g_Portals table at key "portal_name") instead of a custom object in method 2, and therefore using IsInside instead of the chained ands.

Pro/Con: Based entirely on how this is different from method 2 in terms of speed/memory usage, and possibly other things. Can't say I know this.

Method 5:
Use Vector3i for a kind of combination of method 2 and 3. Pro: can't really think of any. Con: Has the downsides of BOTH method 2 and 3 so... probably not.

Method 6:
???


I'm currently leaning towards method 3 as I think that the bad situation in method 3 (extremely large (numbers of) portals) is significantly less likely to happen then the bad situation in method 2/4 (large number of players/portals), and can also be somewhat solved by limiting the size of portals. Also because method 3 is a memory issue (that I believe would have to go to extreme measures to cause noticeable impact) whereas method 2 is a "stops the server really frequently" issue. However, I'd love to know any methods that someone else suggests (ex: a good way to use cCuboid or even Vector3(d/f/i) in a way other than listed above).
Reply
Thanks given by:
#2
When you initialize each portal, instead of storing points, you could use a cBoundingBox. On creating a new portal object, pass two Vector3d objects that define the box's max and min points (MUST be sorted, other words, the min has to contain the lowest coords, the max contains the highest coords). Then use a PlayerOnMoving hook and a for loop to check each portal if the player has walked into it.

-- The Portal meta-object
Portal = {
	Volume = nil,
	Info = ""
}

-- The Portal constructor function
function Portal:new(Min, Max, Info)
	local o = {}
	setmetatable(o, Portal)
	self.__index = self
	o.Volume = cBoundingBox(Min, Max)
	o.Info = Info

	-- Have it insert itself into a global table if desired
	table.insert(g_Portals, o)

	-- And return a reference to the new Portal
	return o
end

Then, to check if the player is inside the portal defined by the bounding box:

function OnPlayerMoving(Player, OldPos, NewPos)
	-- Iterate through each portal
	for _, k in pairs(g_Portals) do
		-- And check if the Players new position is in any portal boxes
		if k.Volume:IsInside(NewPos) then
			-- Do something, grab the k.Info stuff and teleport the player to the desired destination attached to this portal
			-- Also might be a good idea to stop the loop if a match is found to try and reduce overhead
			return true
		end
	end
	return false
end

None of this code is tested, but here's a pretty clean way of going about this.
Reply
Thanks given by:
#3
I'd load all the portals in an X radius around you. When the player reaches the edge you load the next area of portals. That way you don't have to loop through all portals in the world. Just like ProtectionAreas does.
Reply
Thanks given by:




Users browsing this thread: 3 Guest(s)