Advertisement
rockbandcheeseman

GEO 2.3.2

Aug 26th, 2013
147
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 93.85 KB | None | 0 0
  1. -- GEO 2.3.2 (Geometric Environment Objects)
  2.  
  3. function OnGeoEnter(geo, player, objId)
  4.  
  5.     return true
  6. end
  7.  
  8. function OnGeoExit(geo, player, objId)
  9.  
  10.     return true
  11. end
  12.  
  13. --[[
  14.     Documentation:
  15.    
  16.     This script allows the creation of environmental volumes in the shapes of spheres, rectangular prisms, cylinders, rectangles, or circles.
  17.     These environmental volumes allow you to create complex environments with just a few lines of code, including the ability to make a volume a killzone, in which players will immediately die upon entering the volume or any user-defined actions as defined by the functions OnGeoEnter and OnGeoExit.
  18.     GEOs can be returned and manipulated as objects in Lua; this means you can set a variable to be equal to a GEO, making it easy to manipulate in various areas of code.
  19.    
  20.     Changes from GEOs for Phasor 058 and GEOs for 2.0:
  21.    
  22.         -- GEO:damagezone now has the following function header:  GEO:damagezone(boolean, damage).
  23.         -- GEO:freeze has been changed to GEO:unfollow.
  24.        
  25.     Changes in GEO 2.2:
  26.    
  27.         -- Players are now automatically monitored for every GEO.  If you want a GEO to monitor other objects, you still need to use GEO:monitor.
  28.         -- Support for cylinder orientation.  You can now orient your cylinder along the x, y, or z axis.
  29.         -- Added GEO.clear() to destroy all GEOs.
  30.         -- Added GEO:antigeo(); an AntiGEO subtracts from the volume of another GEO.  This allows for you to create complex geometry beyond only spheres, cylinders, and rectangular prisms.  Read docs for more info.
  31.         -- GEO:killzone() improved; now takes three arguments:  <boolean> <delay> <kill_message>
  32.         -- GEO:surface() now works; use this to show 3D representations of your GEOs.
  33.         -- GEO:perimeter() and GEO:surface create objects which are unaffected by physics.
  34.         -- Returning false in OnGeoEnter and OnGeoExit now works on non-player objects.
  35.         -- Various bug fixes
  36.        
  37.     Changes in GEO 2.3.1:
  38.    
  39.         -- GEO:killzone() further improved; now takes infinite arguments:  <boolean> <delay> <kill_message> <function> <args...>
  40.         -- GEO:damagezone() improved; now takes infinite arguments:  <boolean> <damage> <delay> <damage_message> <function> <args...>
  41.         -- GEO:surface() fixed for rectangular prisms (performance should be better too), spheres (the rings are now more uniform), and cylinders (the base of the cylinder was sometimes drawn too high)
  42.         -- Added GEO:vectorintersect(); returns the points at which a vector (given by two points) intersects the given GEO (does not take AntiGEOs into account yet).  Arguments:  <point1>, <point2>
  43.         -- Added GEO:containspoint(); returns a boolean (true or false) if the specified GEO contains the specified point.  Arguments:  <point>
  44.         -- Added GEO:randomcoords(); returns random coordinates within the specified GEO (does not take AntiGEOs into account yet).  No arguments.
  45.         -- Fixed a terrible handling of AntiGEOs (now uses GEO:contains() like it should have in the first place)
  46.    
  47.     GEO Creation Functions:
  48.  
  49.     GEO.newSphere(name, radius, x_coord, y_coord, z_coord)
  50.    
  51.         -- name: <string> Name of this new GEO (must be unique).
  52.         -- radius: <float> Radius of this sphere.
  53.         -- x_coord: <float> X-coordinate of the center of this sphere.
  54.         -- y_coord: <float> Y-coordinate of the center of this sphere.
  55.         -- z_coord: <float> Z-coordinate of the center of this sphere.
  56.        
  57.         Example:
  58.            
  59.             local sphere = GEO.newSphere("Sphere1", 5, 0, 0, 0)
  60.            
  61.         Notes:
  62.        
  63.             GEO.newSphere creates a spherical GEO with the specified name (keep in mind GEO names must be unique from each other), radius, and x, y, and z-coordinates of the center of the sphere. Returns the sphere as an object.
  64.  
  65.     GEO.newRectPrism(name, x_length, y_length, z_length, x_coord, y_coord, z_coord)
  66.    
  67.         -- name: <string> Name of this new GEO (must be unique).
  68.         -- x_length: <float> Length of this rectangular prism in the X direction.
  69.         -- y_length: <float> Length of this rectangular prism in the Y direction.
  70.         -- z_length: <float> Length of this rectangular prism in the Z direction.
  71.         -- x_coord: <float> X-coordinate of the center of this rectangular prism.
  72.         -- y_coord: <float> Y-coordinate of the center of this rectangular prism.
  73.         -- z_coord: <float> Z-coordinate of the center of this rectangular prism.
  74.    
  75.         Example:
  76.  
  77.             local rectprism = GEO.newRectPrism("RectPrism1", 3, 3, 10, 0, 0, 5)
  78.            
  79.         Notes:
  80.            
  81.             GEO.newRectPrism creates a rectangular prism GEO with the specified name (keep in mind GEO names must be unique from each other), x, y, and z lengths, and x, y, and z-coordinates of the center of the rectangular prism. Returns the rectangular prism as an object.
  82.  
  83.     GEO.newCylinder(name, radius, height, x_coord, y_coord, z_coord, [orientation])
  84.    
  85.         -- name: <string> Name of this new GEO (must be unique).
  86.         -- radius: <float> Radius of this cylinder.
  87.         -- height: <float> Height of this cylinder.
  88.         -- x_coord: <float> X-coordinate of the center of this cylinder.
  89.         -- y_coord: <float> Y-coordinate of the center of this cylinder.
  90.         -- z_coord: <float> Z-coordinate of the center of this cylinder.
  91.         -- orientation: <string> Axis of orientation ("x", "y", or "z") (default = "z").
  92.    
  93.         Example:
  94.        
  95.             local cylinder = GEO.newCylinder("Cylinder1", 3, 10, 0, 0, 0)
  96.    
  97.         Notes:
  98.            
  99.             GEO.newCylinder creates a cylindrical GEO with the specified name (keep in mind GEO names must be unique from each other), radius, height, and x, y, and z-coordinates of the center of the cylinder. Returns the cylinder as an object.
  100.  
  101.     GEO.newRect(name, width, height, x_coord, y_coord, z_coord, [orientation])
  102.    
  103.         -- name: <string> Name of this new GEO (must be unique).
  104.         -- width: <float> Width of this rectangle.
  105.         -- height: <float> Height of this rectangle.
  106.         -- x_coord: <float> X-coordinate of the center of this rectangle.
  107.         -- y_coord: <float> Y-coordinate of the center of this rectangle.
  108.         -- z_coord: <float> Z-coordinate of the center of this rectangle.
  109.         -- orientation: <string> Axis of orientation ("x", "y", or "z") (default = "z").
  110.        
  111.         Example:
  112.  
  113.             local rectangle = GEO.newRect("Rect1", 5, 10, 0, 0, 5, "x")
  114.            
  115.         Notes:
  116.        
  117.             GEO.newRect creates a rectangular GEO with the specified name (keep in mind GEO names must be unique from each other), width, height, x, y, and z-coordinates of the center of the rectangle, and orientation. The orientation specifies which axis ("x", "y", or "z") has a length of 0 in the rectangle. For example, by default, orientation is "z". This means you would see the rectangle if you were looking down on top of it (or up from underneath). You can also think of the orientation axis as being the axis that punches through the center of the object.
  118.  
  119.     GEO.newCircle(name, radius, x_coord, y_coord, z_coord, [orientation])
  120.    
  121.         -- name: <string> Name of this new GEO (must be unique).
  122.         -- radius: <float> Radius of this circle.
  123.         -- x_coord: <float> X-coordinate of the center of this circle.
  124.         -- y_coord: <float> Y-coordinate of the center of this circle.
  125.         -- z_coord: <float> Z-coordinate of the center of this circle.
  126.         -- orientation: <string> Axis of orientation ("x", "y", or "z") (default = "z")
  127.        
  128.         Example:
  129.  
  130.             local circle = GEO.newCircle("Circle1", 10, 0, 0, 5, "x")
  131.  
  132.         Notes:
  133.        
  134.             GEO.newCircle creates a circular GEO with the specified name (keep in mind GEO names must be unique from each other), radius, x, y, and z-coordinates of the center of the circle, and orientation. The orientation specifies which axis ("x", "y", or "z") has a length of 0 in the circle. For example, by default, orientation is "z". This means you would see the circle if you were looking down on top of it (or up from underneath). You can also think of the orientation axis as being the axis that punches through the center of the object.
  135.  
  136.  
  137.     Once you've created your GEO, you want to make it do something. The following are GEO-editing functions.
  138.     Note that the following functions are members of the GEO metatable. This means to call the function, you specify the GEO variable before the function (separated by a colon). See the functions below for more specific examples.
  139.    
  140.     GEO Editing Functions:
  141.  
  142.     GEO:delete()
  143.    
  144.         Example:
  145.  
  146.             local sphere = GEO.newSphere("Sphere", 5, 0, 0, 0)
  147.             sphere:delete()
  148.  
  149.         Notes:
  150.            
  151.             Deletes the specified GEO.
  152.    
  153.     GEO:move(x_coord, y_coord, z_coord)
  154.    
  155.         -- x_coord: <float> New x-coordinate of the center of this GEO.
  156.         -- y_coord: <float> New y-coordinate of the center of this GEO.
  157.         -- z_coord: <float> New z-coordinate of the center of this GEO.
  158.    
  159.         Example:
  160.  
  161.             local sphere = GEO.newSphere("Sphere", 5, 0, 0, 0)
  162.             sphere:move(0, 0, 10)
  163.        
  164.         Notes:
  165.    
  166.             Moves the center of the specified GEO to the specified x, y, and z-coordinates.
  167.  
  168.     GEO:radius([new_radius])
  169.    
  170.         -- new_radius: <float> New radius of specified GEO (if no radius is specified, the radius of the GEO is returned).
  171.    
  172.         Example:
  173.  
  174.             local cylinder = GEO.newCylinder("Cylinder", 10, 20, 0, 0, 0)
  175.             local cyl_radius = cylinder:radius()
  176.             if cyl_radius < 20 then
  177.                 cylinder:radius(20)
  178.             end
  179.            
  180.         Notes:
  181.  
  182.             If a new_radius is specified, changes the radius of the specified GEO to the value of new_radius. Otherwise, returns the radius of the specified GEO. Note that this can only be used for spheres, circles, and cylinders.
  183.  
  184.     GEO:size([x_length], [y_length], [z_length])
  185.    
  186.         -- x_length: <float> New length of GEO in the x direction.
  187.         -- y_length: <float> New length of GEO in the y direction.
  188.         -- z_length: <float> New length of GEO in the z direction.
  189.         -- Read the notes for descriptions about how the optional parameters work in this function.
  190.    
  191.         Example:
  192.  
  193.             -- Create three GEOs
  194.             local rectprism = GEO.newRectPrism("RectPrism", 5, 5, 10, 0, 0, 5)
  195.             local rectangle = GEO.newRect("Rect", 5, 10, 0, 0, 5, "y")
  196.             local cylinder = GEO.newCylinder("Cylinder", 5, 20, 0, 0, 10)
  197.  
  198.             -- Get sizes of all three shapes
  199.             local rpx, rpy, rpz = rectprism:size()
  200.             local rw, rh = rectangle:size()
  201.             local cyl_height = cylinder:size()
  202.  
  203.             -- Double their sizes
  204.             rectprism:size(rpx * 2, rpy * 2, rpz * 2)
  205.             rectangle:size(rw * 2, 0, rh * 2)
  206.             cylinder:size(cyl_height * 2)
  207.  
  208.         Notes:
  209.    
  210.             If any coordinate is specified, changes the GEO's size in the x, y, and z directions respectively. If no coordinate is specified in any direction, this returns the size of the object. Note this can only be used for rectangular prisms, rectangles, and cylinders. Also note if you're attempting to retrieve the size of an object, rectangular prisms will return three values (x, y, and z), rectangles will return two values (width and height), and cylinders will return one value (height). Also note that when changing the height of a cylinder, you need only specify one number (since you're only changing the height). If you're trying to change the size of a rectangle, keep its orientation in mind when changing the x, y, and z lengths (changes in length of the axis of which the rectangle is oriented will be ignored).
  211.    
  212.     GEO:contains(objId)
  213.    
  214.         objId: <object id> Object being tested to see if it is inside of the specfied GEO.
  215.    
  216.         Example:
  217.  
  218.             local sphere = GEO.newSphere("Sphere", 5, 0, 0, 0)
  219.  
  220.             function OnPlayerSpawn(player)
  221.            
  222.                 local m_player = getplayer(player)
  223.                 local objId = readdword(m_player, 0x34)
  224.  
  225.                 if sphere:contains(objId) then
  226.                     say(getname(player) .. " has spawned inside of the sphere")
  227.                 end
  228.             end
  229.            
  230.         Notes:
  231.    
  232.             Checks if the specified objId is within the specified GEO. Returns true or false.
  233.            
  234.     GEO:containspoint(point)
  235.    
  236.         point: <table> Coordinates being checked.
  237.        
  238.         Example:
  239.        
  240.             local sphere = GEO.newSphere("Sphere", 5, 0, 0, 0)
  241.             local point = {10, 10, 0}
  242.             local insphere = GEO:containspoint(point)
  243.             if insphere(point) then
  244.                 say("(10, 10, 0) is in the sphere!")
  245.             end
  246.            
  247.         Notes:
  248.        
  249.             Make sure the table passed is in {x, y, z} format.
  250.            
  251.     GEO:vectorintersect(pointA, pointB)
  252.    
  253.         pointA: <table> First point in vector.
  254.         pointB: <table> Second point in vector.
  255.        
  256.         Example:
  257.        
  258.             -- This is a poor example that shows simply functionality
  259.             sphere = GEO.newSphere("sphere", 5, 0, 0, 0)
  260.            
  261.             function OnPlayerSpawn(player)
  262.                
  263.                 local m_player = getplayer(player)
  264.                 local objId = readdword(m_player, 0x34)
  265.                 local x, y, z = getobjectcoords(objId)
  266.                 local point1, point2 = sphere:vectorintersect({x, y, z}, {x, y, z + 10})  -- This will check if the line between where the player is standing and 10 units above them ever intersects the sphere.
  267.                 if point1 then
  268.                     say("x: " .. point1[1] .. " y: " .. point1[2] .. " z: " .. point1[3])
  269.                 end
  270.                
  271.                 if point2 then
  272.                     say("x: " .. point2[1] .. " y: " .. point2[2] .. " z: " .. point2[3])
  273.                 end
  274.             end
  275.            
  276.         Notes:
  277.        
  278.             This function can return as many as two intersection points and as few as zero.  If a point does not exist, this function returns nil for that point.  This function does not yet take AntiGEOs into account.  Make sure the tables for the points passed are in {x, y, z} format.
  279.  
  280.     GEO:follow(objId)
  281.    
  282.         objId: <object id> Object this GEO should follow.
  283.    
  284.         Example:
  285.  
  286.             function OnPlayerSpawn(player)
  287.  
  288.                 local m_player = getplayer(player)
  289.                 local objId = readdword(m_player, 0x34)
  290.                
  291.                 -- Create a sphere with the objId in the name so you know it is unique
  292.                 local sphere = GEO.newSphere("Sphere" .. objId, 5, 0, 0, 0)
  293.                 sphere:follow(objId)
  294.             end
  295.            
  296.         Notes:
  297.        
  298.             Sets a GEO to follow the specified objId. The GEO will be automatically deleted if the object it is following has also been deleted.
  299.  
  300.  
  301.     GEO:unfollow()
  302.    
  303.         Example:
  304.  
  305.             function OnPlayerSpawn(player)
  306.            
  307.                 local m_player = getplayer(player)
  308.                 local objId = readdword(m_player, 0x34)
  309.  
  310.                 -- Create a sphere with the objId in the name so you know it is unique
  311.                 local sphere = GEO.newSphere("Sphere" .. objId, 5, 0, 0, 0)
  312.                 sphere:follow(objId)
  313.                 sphere:unfollow()
  314.             end
  315.  
  316.         Notes:
  317.        
  318.             Tells the specified GEO to stop following the objId it is currently following.
  319.  
  320.     GEO:velocity(x_velocity, y_velocity, z_velocity)
  321.    
  322.         x_velocity: <float> GEO's velocity in the x direction.
  323.         y_velocity: <float> GEO's velocity in the y direction.
  324.         z_velocity: <float> GEO's velocity in the z direction.
  325.    
  326.         Example:
  327.  
  328.             local rectangle = GEO.newRectPrism("RectPrism", math.inf, math.inf, 100, 0, 0, -50)
  329.             rectangle:velocity(0, 0, 0.1)
  330.  
  331.         Notes:
  332.    
  333.             Sets the velocity of the specified GEO in the x, y, and z directions.
  334.    
  335.     GEO:killzone([boolean], [delay], [kill_message], [func], [args...])
  336.    
  337.         boolean: <boolean> The ability of this GEO to be a killzone (if no boolean is specified, the current killzone boolean is returned).
  338.         delay: <int> The time (in seconds) the GEO should wait to kill a player after they enter.
  339.         kill_message: <string> The message a player will receive when they are killed by this GEO.
  340.         func: <function> A special function that MUST return true or false and MUST pass the GEO, then player as its first two arguments.
  341.         args...: <data> Additional arguments to be passed into the function.
  342.    
  343.         Example:
  344.  
  345.             -- Kill a player immediately for entering a sphere
  346.             local killsphere = GEO.newSphere("Sphere", 10, 0, 0, 0)
  347.             killsphere:killzone(true)
  348.            
  349.             function isredwednesday(geo, player, day)
  350.            
  351.                 if getteam(player) == 0 then
  352.                     if day == "Wednesday" then
  353.                         return true
  354.                     end
  355.                 end
  356.                
  357.                 return false
  358.             end
  359.            
  360.             -- Kill a player after being in a cube for 5 seconds if they're on the Red Team and today is Wednesday.
  361.             local killcube = GEO.newRectPrism("Cube", 5, 5, 5, 10, -10, 5)
  362.             killcube:killzone(true, 5, "Since you're on the red team and were in this cube for five seconds and today is a Wednesday, you shall now die.", isredwednesday, os.date("%A"))
  363.  
  364.         Notes:
  365.    
  366.             Toggles the ability for a GEO to be a killzone. If a function is specified, the function header's first two arguments MUST be the geo, then the player (such as in the example above) and returns true if the player should be killed and false if the player should not be. If a GEO is a killzone and no delay is specified or the delay = 0, any player to enter the GEO will automatically die. If a delay is specified, the player will be killed after the amount of seconds specified by delay. If a kill_message is specified, the player will receive that message when they die. Otherwise, no message will be sent.
  367.    
  368.     GEO:damagezone([boolean], [damage], [delay], [damage_message], [func], [args...])
  369.    
  370.         boolean: <boolean> Indicates whether or not this GEO is a damagezone (if no boolean is specified, the current damagezone boolean is returned).
  371.         damage: <float> Amount of damage per second this GEO does to a player within it.
  372.         delay: <int> The time (in seconds) the GEO should wait to damage a player after they enter.
  373.         damage_message: <string> The message a player will receive when they are initially damaged by this GEO.
  374.         func: <function> A special function that MUST return true or false and MUST pass the GEO, then player as its first two arguments.
  375.         args...: <data> Additional arguments to be passed into the function.
  376.            
  377.         Example:
  378.  
  379.             -- Damage a player as soon as they enter this sphere.
  380.             local damagesphere = GEO.newSphere("Sphere", 10, 0, 0, 0)
  381.             damagesphere:damagezone(true, 10)
  382.            
  383.             -- Damage a player after 10 seconds if they are named Nuggets and there is a GEO called "Sphere".
  384.             function isnuggetssphere(geo, player, sphere_exists)
  385.            
  386.                 if getname(player) == "Nuggets" then
  387.                     if sphere_exists then
  388.                         return true
  389.                     end
  390.                 end
  391.                
  392.                 return false
  393.             end
  394.            
  395.             local damagecylinder = GEO.newCylinder("Cylinder", 5, 10, 0, 0, 5)
  396.             damagecylinder:damagezone(true, 50, 10, "Your name is Nuggets and there is a sphere somewhere around here.", isnuggetssphere, GEO.get("Sphere"))
  397.  
  398.         Notes:
  399.            
  400.             Toggles the ability for a GEO to be a damagezone. If a GEO is a damagezone, the player will be damaaged at the rate you specify.
  401.  
  402.     GEO:face(orientation, direction)
  403.    
  404.         orientation: <string> The axis on which the face will be created ("x", "y", or "z").
  405.         direction: <string> The specification of the "top" or "bottom" face in the specified orientation ("+" or "-").
  406.    
  407.         Example:
  408.  
  409.             local cylinder = GEO.newCylinder("Cylinder", 5, 10, 0, 0, 5)
  410.             local topcircle = cylinder:face("z", "+")
  411.             local bottomcircle = cylinder:face("z", "-")
  412.  
  413.         Notes:
  414.    
  415.             Cuts the face of the specified three-dimensional GEO off to create a two-dimensional GEO given the orientation ("x", "y", or "z") and direction ("+" or "-"). For example, if the orientation is "z" and the direction is "+", and you're passing a rectangular prism, you will end up with the a rectangle with the same position and size as the top face of your rectangular prism. Note you can only use this for rectangular prisms and cylinders. Also note that for cylinders, you may only use the "z" orientation. Returns the new two-dimensional GEO as an object.
  416.    
  417.     GEO:extend(orientation, direction, amount)
  418.    
  419.         orientation: <string> The axis on which the face will be extended ("x", "y", or "z").
  420.         direction: <string> The specification of the "top" or "bottom" face in the specified orientation ("+" or "-").
  421.         amount: <float> Distance the specified face will be extended.
  422.    
  423.         Example:
  424.  
  425.             local cylinder = GEO.newCylinder("Cylinder", 5, 10, 0, 0, 5)
  426.             cylinder:extend("z", "+", 10)
  427.  
  428.         Notes:
  429.    
  430.             Extends the specified face by the specified amount (see the function face for how to specify orientation and direction). Note that this may only be used on rectangular prisms, rectangles, and cylinders. Also note that extending a face does not change the object's center. If you use GEO:surface() after using GEO:extend(), you will not see the changes GEO:extend() has made.
  431.            
  432.     GEO:antigeo([boolean, geos...])
  433.    
  434.         boolean: <boolean> Toggles whether or not this is an AntiGEO.
  435.         geos...: <data> Defines for which GEOs this is an AntiGEO (infinite number of argumnets).
  436.        
  437.         Example:
  438.            
  439.             -- Battle Creek Blue Base Roof
  440.             blue_base_roof = GEO.newRectPrism("blue_roof", 9.5, 7.8, 1.3, 1.95, 13.9, 1.85)
  441.             blue_roof_anti = GEO.newRectPrism("blue_roof_anti", 10, 10, 2, 28.75, 14, 1.5)
  442.             -- Cut out the section of the blue_base_roof GEO that is inside of the room where the CTF flag is
  443.             blue_roof_anti:antigeo(true, blue_base_roof)
  444.            
  445.         Notes:
  446.        
  447.             This function works like object subtraction in CAD programs. If you are unfamiliar with this, this link should help describe it: http://codevisually.com/wp-content/uploads/2011/12/cvdec1_06.jpg (look at the a.subtract(b) picture). If you are within a GEO and enter one of its AntiGEOs, it will count as exiting the first GEO. Keep in mind that AntiGEOs are also GEOs and may have their own AntiGEOs. If no boolean or GEOs are specified, this returns a boolean (true or false) describing if this GEO is an AntiGEO. When using GEO:surface() or GEO:perimeter on a GEO, all of that GEO's AntiGEOs will be shown as well with Overshield powerups.
  448.    
  449.     GEO:copy([name])
  450.    
  451.         name: <string> Name given to the copy of the specified GEO (if no name is specified, a default name is given).
  452.        
  453.         Example:
  454.  
  455.             local sphere = GEO.newSphere("Sphere", 5, 0, 0, 0)
  456.             local sphere2 = sphere:copy()
  457.  
  458.         Notes:
  459.        
  460.             Returns a copy of the specified GEO with the specified name. If no name is specified, a default name will be given.
  461.    
  462.     GEO:monitor(objId)
  463.    
  464.         objId: <object id> The object this GEO should monitor for entering/exiting.
  465.    
  466.         Example:
  467.  
  468.             local rectprism = GEO.newRectPrism("RectPrism", math.inf, math.inf, 100, 0, 0, -50)
  469.  
  470.             function OnObjectCreation(objId)
  471.            
  472.                 local m_object = getobject(objId)
  473.                 local mapId = readdword(m_object)
  474.                 local _, tagtype = gettaginfo(mapId)
  475.                
  476.                 if tagtype == "vehi" then
  477.                     rectprism:monitor()  -- Monitor all vehicles
  478.                 end
  479.             end
  480.        
  481.         Notes:
  482.    
  483.             Tells the specified GEO to monitor when objId enters and exits it. Note that this must be used in order for OnGeoEnter and OnGeoExit to work for the specified GEO and objId. This is for the sake of the script not having to check the coordinates of every single object and comparing them to the volumes of every single GEO every 1/100 of a second. Also note that as of GEO 2.2, it is not necessary to use this function on players; GEOs will automatically monitor players.
  484.  
  485.     GEO:coords()
  486.    
  487.         Example:
  488.  
  489.             local sphere = GEO.newSphere("Sphere", 10, 0, 0, 0)
  490.             local x, y, z = sphere:coords()
  491.  
  492.         Notes:
  493.    
  494.             Returns the x, y, and z coordinates of the center of the specified GEO.
  495.    
  496.  
  497.     GEO:high(orientation)
  498.    
  499.         orientation: <string> Axis of which you're attempting to find the highest point ("x", "y", or "z").
  500.        
  501.         Example:
  502.  
  503.             local sphere = GEO.newSphere("Sphere", 10, 0, 0, 0)
  504.             local zhigh = sphere:high("z")
  505.  
  506.         Notes:
  507.    
  508.             Returns the highest coordinate in the specified orientation (i.e. geo:high("z") would return the highest z coordinate still considered to be inside of the GEO).
  509.  
  510.     GEO:low(orientation)
  511.    
  512.         orientation: <string> Axis of which you're attempting to find the lowest point ("x", "y", or "z")
  513.    
  514.         Example:
  515.  
  516.             local sphere = GEO.newSphere("Sphere", 10, 0, 0, 0)
  517.             local zlow = sphere:low("z")
  518.  
  519.         Notes:
  520.    
  521.             Returns the lowest coordinate in the specified orientation (i.e. geo:low("z") would return the lowest z coordinate still considered to be inside of the GEO).
  522.  
  523.     GEO:randomcoords()
  524.    
  525.         Example:
  526.  
  527.             local cylinder = GEO.newCylinder("Cylinder", 5, 10, 0, 0, 0)
  528.             local x, y, z = cylinder:randomcoords()
  529.  
  530.         Notes:
  531.    
  532.             Returns random x, y, and z coordinates within the specified GEO.
  533.  
  534.     GEO:type()
  535.    
  536.         Example:
  537.  
  538.             local cylinder = GEO.newCylinder("Cylinder", 5, 10, 0, 0, 0)
  539.             local type = cylinder:type()
  540.            
  541.         Notes:
  542.    
  543.             Returns the type of the specified GEO as a string.
  544.  
  545.     GEO:orientation()
  546.    
  547.         Example:
  548.  
  549.             local cylinder = GEO.newCylinder("Cylinder", 5, 10, 0, 0, 0)
  550.             local orientation = cylinder:orientation()
  551.            
  552.         Notes:
  553.    
  554.             Returns the orientation of a 2-dimensional GEO or Cylinder. Note that this cannot be used on Spheres or RectPrisms.
  555.  
  556.     GEO:name()
  557.    
  558.         Example:
  559.  
  560.             local sphere = GEO.newSphere("Sphere", 5, 0, 0, 0)
  561.             local name = sphere:name()
  562.  
  563.         Notes:
  564.    
  565.             Returns the name of the specified GEO.
  566.  
  567.  
  568.     GEO:perimeter([density], [mapId])
  569.    
  570.         density: <int> Amount of objects that should spawn around the perimeter of the GEO.
  571.         mapId: <tag id> The tag of the objects that should spawn around the GEO's perimeter (default = Full-Spectrum Vision)
  572.    
  573.         Example:
  574.  
  575.             local sphere = GEO.newSphere("sphere", 5, 0, 0, 0.5)
  576.             sphere:perimeter(20)
  577.             local rectprism = GEO.newRectPrism("rectprism", 3, 4, 5, 0, 0, 0.5)
  578.             rectprism:perimeter(7, gettagid("weap", "weapons\\flag\\flag"))
  579.            
  580.         Notes:
  581.    
  582.             Spawns the specified object (given via mapId) with the specified density (default 10) about the perimeter of the Z-center of the specified GEO. For spheres and cylinders, the amount of objects spawned will be equal to the density. For rectangular prisms, the density specifies the amount of objects spawned per edge (so a density of 10 will spawn a total of 40 objects). You may want to make sure the GEO's center is slightly above the ground so the objects don't spawn under the map. Also be careful when this function is used, as it does create a lot of objects. If you aren't careful, you'll reach Halo's object limit.
  583.            
  584.     GEO:surface([density], [mapId])
  585.    
  586.         density: <int> Amount of objects that should spawn to create a 3D representation of the GEO.
  587.         mapId: <tag id> The tag of the objects that should spawn to create the 3D representation of the GEO (default = Full-Spectrum Vision)
  588.        
  589.         Example:
  590.            
  591.             local sphere = GEO.newSphere("sphere", 5, 0, 0, 1)
  592.             sphere:surface()
  593.             local cylinder = GEO.newCylinder("cylinder", 4, 7, 0, 0, 1, "x")
  594.             cylinder:surface(20, gettagid("weap", "weapons\\plasma grenade\\plasma grenade"))
  595.            
  596.         Notes:
  597.        
  598.             Spawns the specified object (given via mapId) with the specified density (default 12) to create a 3D representation of the specified GEO. Cannot be used on Circles or Rectangles. The density of these approximate an appropriate amount of objects to be made in order to get a good idea of where a GEO is. It is highly suggested that you use this function only as a means to aid in visualization during testing. This function creates a lot of objects and it is very easy to hit the object limit using it.
  599.  
  600.  
  601.     OnGeoEnter and OnGeoExit Event Functions:
  602.  
  603.     OnGeoEnter(geo, player, objId)
  604.        
  605.         geo: <GEO object> The GEO the player or object is entering.
  606.         player: <player memory id> The player entering the GEO.
  607.         objId: <object id> The objId entering the GEO.
  608.    
  609.         Example:
  610.  
  611.             sphere = GEO.newSphere("sphere", 5, 0, 0, 0)
  612.  
  613.             function OnGeoEnter(geo, player, objId)
  614.  
  615.                 if geo == sphere then
  616.                     privatesay(player, "You may not enter this GEO.")
  617.                     return false
  618.                 end
  619.                
  620.                 return true
  621.             end
  622.        
  623.         Notes:
  624.    
  625.             Called when an object that is being monitored by a GEO enters that GEO. Return 1 to allow objects to enter, return 0 to disallow this.
  626.  
  627.     OnGeoExit(geo, player, objId)
  628.    
  629.         geo: <GEO object> The GEO the player or object is exiting.
  630.         player: <player memory id> The player exiting the GEO.
  631.         objId: <object id> The objId exiting the GEO.
  632.    
  633.         Example:
  634.  
  635.             sphere = GEO.newSphere("sphere", 5, 0, 0, 0)
  636.            
  637.             function OnGeoExit(geo, player, objId)
  638.  
  639.                 if geo == sphere then
  640.                     privatesay(player, "You may not exit this GEO.")
  641.                     return false
  642.                 end
  643.                
  644.                 return true
  645.             end
  646.  
  647.         Notes:
  648.    
  649.             Called when an object that is being monitored by a GEO exits that GEO. Return 1 to allow objects to exit, return 0 to disallow this.
  650.  
  651.  
  652.     Miscellaneous GEO functions:
  653.  
  654.     GEO.get(name)
  655.    
  656.         name: <string> Name of the GEO you're trying to access.
  657.    
  658.         Example:
  659.  
  660.             function OnPlayerSpawn(player)
  661.            
  662.                 local m_player = getplayer(player)
  663.                 local objId = readdword(m_player, 0x34)
  664.                 local sphere = GEO.newSphere("Sphere" .. objId, 5, 0, 0, 0)
  665.                 sphere:follow(objId)
  666.             end
  667.  
  668.             function OnPlayerKill(killer, victim, mode)
  669.  
  670.                 local m_victim = getplayer(victim)
  671.                 local v_objectId = readdword(m_victim, 0x34)
  672.                 local victim_sphere = GEO.get("Sphere" .. v_objectId)
  673.                 victim_sphere:unfollow()
  674.             end
  675.  
  676.         Notes:
  677.    
  678.             Returns a GEO given the specified name.
  679.  
  680.     GEO.followedBy(objId)
  681.    
  682.         objId: <object id> Object being followed by GEOs.
  683.    
  684.         Example:
  685.  
  686.             function OnPlayerKill(killer, victim, mode)
  687.  
  688.                 local geos = GEO.followedBy(victim)
  689.                 for k,v in ipairs(geos) do
  690.                     v:unfollow()
  691.                 end
  692.             end
  693.  
  694.         Notes:
  695.    
  696.             Returns a list of GEOs following the specified objId.
  697.    
  698.     GEO.cleanup(player)
  699.    
  700.         player: <player memory id> Player whose following of GEOs you want to delete.
  701.    
  702.         Example:
  703.  
  704.             -- Note that this is here only for the sake of example; this is not necessary, as the GEOTimer takes care of this automatically.
  705.             function OnPlayerKill(killer, victim, mode)
  706.  
  707.                 GEO.cleanup(victim)
  708.             end
  709.  
  710.         Notes:
  711.    
  712.             Deletes all of the GEOs currently following the specified player.
  713.            
  714.     GEO.clear()
  715.    
  716.         Example:
  717.        
  718.             function OnNewGame(map)
  719.            
  720.                 -- Global variable for map name
  721.                 cur_map = map
  722.             end
  723.            
  724.             function OnServerChat(player, message, type)
  725.                
  726.                 if message == "reset" then
  727.                     svcmd("sv_map_reset")
  728.                     GEO.clear()
  729.                     OnNewGame(cur_map)
  730.                 end
  731.             end
  732.            
  733.         Notes:
  734.        
  735.             Deletes all GEOs currently active on the map.
  736.  
  737.    
  738.     If you have any questions about how GEOs work, PM me (Nuggets) at phasor.proboards.com.
  739. --]]
  740.  
  741. -- GEO --
  742.  
  743. -- Create Metatable
  744. GEO = {}
  745. GEO.__index = GEO
  746.  
  747. -- Set random seed and define infinity
  748. math.randomseed(os.time())
  749. math.inf = 1 / 0
  750.  
  751. function inSphere(objId, x, y, z, r)
  752.  
  753.     local ox, oy, oz = getobjectcoords(objId)
  754.     if ox then
  755.         -- Pythagorean
  756.         local dist = math.sqrt((x - ox) ^ 2 + (y - oy) ^ 2 + (z - oz) ^ 2)
  757.         if dist <= r then
  758.             return true
  759.         end
  760.     end
  761.        
  762.     return false
  763. end
  764.  
  765. function inCircle(objId, x, y, z, r, orientation)
  766.  
  767.     local ox, oy, oz = getobjectcoords(objId)
  768.     if ox then
  769.         -- Default orientation to "z"
  770.         orientation = orientation or "z"
  771.         -- Pythagorean based on circle's orientation
  772.         if orientation == "z" then
  773.             local dist = math.sqrt((x - ox) ^ 2 + (y - oy) ^ 2)
  774.             if dist <= r and oz == z then
  775.                 return true
  776.             end
  777.         elseif orientation == "y" then
  778.             local dist = math.sqrt((x - ox) ^ 2 + (z - oz) ^ 2)
  779.             if dist <= r and oy == y then
  780.                 return true
  781.             end
  782.         elseif orientation == "x" then
  783.             local dist = math.sqrt((y - oy) ^ 2 + (z - oz) ^ 2)
  784.             if dist <= r and ox == x then
  785.                 return true
  786.             end
  787.         end
  788.     end
  789.        
  790.     return false
  791. end
  792.  
  793. function inCylinder(objId, x, y, z, hlow, hhigh, r, orientation)
  794.  
  795.     local ox, oy, oz = getobjectcoords(objId)
  796.     if ox then
  797.         if orientation == "z" then
  798.             -- Pythagorean to see if object is within radius of circle
  799.             local dist = math.sqrt((x - ox) ^ 2 + (y - oy) ^ 2)
  800.             -- Make sure the object is also within the height of the cylinder
  801.             if dist <= r and oz >= hlow and oz <= hhigh then
  802.                 return true
  803.             end
  804.         elseif orientation == "x" then
  805.             local dist = math.sqrt((y - oy) ^ 2 + (z - oz) ^ 2)
  806.             if dist <= r and ox >= hlow and ox <= hhigh then
  807.                 return true
  808.             end
  809.         elseif orientation == "y" then
  810.             local dist = math.sqrt((z - oz) ^ 2 + (x - ox) ^ 2)
  811.             if dist <= r and oy >= hlow and oy <= hhigh then
  812.                 return true
  813.             end
  814.         end
  815.     end
  816.        
  817.     return false
  818. end
  819.  
  820. function inRectangle(objId, xlow, xhigh, ylow, yhigh, zlow, zhigh)
  821.  
  822.     -- These functions are essentially the same
  823.     return inRectPrism(objId, xlow, xhigh, ylow, yhigh, zlow, zhigh)
  824. end
  825.  
  826. function inRectPrism(objId, xlow, xhigh, ylow, yhigh, zlow, zhigh)
  827.  
  828.     local x, y, z = getobjectcoords(objId)
  829.     if x then
  830.         -- Make sure the coordinates are inside of each extreme of the rectangular prism
  831.         if x <= xhigh and x >= xlow and y <= yhigh and y >= ylow and z <= zhigh and z >= zlow then
  832.             return true
  833.         end
  834.     end
  835.        
  836.     return false
  837. end
  838.  
  839. function randomInSphere(x, y, z, r)
  840.  
  841.     -- Increase precision
  842.     x = math.floor(x * 100)
  843.     y = math.floor(y * 100)
  844.     z = math.floor(z * 100)
  845.     r = math.floor(r * 100)
  846.        
  847.     -- Find random values inside of the sphere.
  848.     return math.random(x - r, x + r + 1) / 100, math.random(y - r, y + r + 1) / 100, math.random(z - r, z + r + 1) / 100
  849. end
  850.  
  851. function randomInCircle(x, y, z, r, orientation)
  852.  
  853.     -- Increase precision
  854.     r = math.floor(r * 100)
  855.        
  856.     -- Possible values depend on circle's orientation.
  857.     if orientation == "z" then
  858.         x = math.floor(x * 100)
  859.         y = math.floor(y * 100)
  860.         return math.random(x - r, x + r + 1) / 100, math.random(y - r, y + r + 1) / 100, z    
  861.     elseif orientation == "x" then
  862.         y = math.floor(y * 100)
  863.         z = math.floor(z * 100)
  864.         return x, math.random(y - r, y + r + 1) / 100, math.random(z - r, z + r + 1) / 100
  865.     elseif orientation == "y" then
  866.         x = math.floor(x * 100)
  867.         z = math.floor(z * 100)
  868.         return math.random(x - r, x + r + 1) / 100, y, math.random(z - r, z + r + 1) / 100
  869.     end
  870. end
  871.  
  872. function randomInCylinder(x, y, z, hlow, hhigh, r, orientation)
  873.  
  874.     -- Increase precision
  875.     x = math.floor(x * 100)
  876.     y = math.floor(y * 100)
  877.     z = math.floor(z * 100)
  878.     hlow = math.floor(hlow * 100)
  879.     hhigh = math.floor(hhigh * 100)
  880.     r = math.floor(r * 100)
  881.        
  882.     -- Find random values inside of the cylinder depending on its orientation.
  883.     if orientation == "z" then
  884.         return math.random(x - r, x + r + 1) / 100, math.random(y - r, y + r + 1) / 100, math.random(hlow, hhigh + 1) / 100
  885.     elseif orientation == "y" then
  886.         return math.random(x - r, x + r + 1) / 100, math.random(hlow, hhigh + 1) / 100, math.random(z - r, z + r + 1) / 100
  887.     elseif orientation == "x" then
  888.         return math.random(hlow, hhigh + 1) / 100, math.random(y - r, r + r + 1) / 100, math.random(z - r, z + r + 1) / 100
  889.     end
  890. end
  891.  
  892. function randomInRectPrism(xlow, xhigh, ylow, yhigh, zlow, zhigh)
  893.  
  894.     -- Increase precision
  895.     xlow = math.floor(xlow * 100)
  896.     xhigh = math.floor(xhigh * 100)
  897.     ylow = math.floor(ylow * 100)
  898.     yhigh = math.floor(yhigh * 100)
  899.     zlow = math.floor(zlow * 100)
  900.     zhigh = math.floor(zhigh * 100)
  901.        
  902.     -- Find random values inside of the rectangular prism.
  903.     return math.random(xlow, xhigh + 1) / 100, math.random(ylow, yhigh + 1) / 100, math.random(zlow, zhigh)
  904. end
  905.  
  906. function sphereintersect(sphere, pointA, pointB)
  907.  
  908.     local points = {}
  909.  
  910.     local a = (pointA.x - pointB.x) ^ 2 + (pointA.y - pointB.y) ^ 2 + (pointA.z - pointB.z) ^ 2
  911.     local c = (pointA.x - sphere.x) ^ 2 + (pointA.y - sphere.y) ^ 2 + (pointA.z - sphere.z) ^ 2 - sphere.r ^ 2
  912.     local b = (pointB.x - sphere.x) ^ 2 + (pointB.y - sphere.y) ^ 2 + (pointB.z - sphere.z) ^ 2 - c - a - sphere.r ^ 2
  913.  
  914.     -- Make sure square root is not imaginary.
  915.     local q = b ^ 2 - 4 * a * c
  916.  
  917.     if q > 0 then
  918.        
  919.         -- Quadratic Formula:
  920.         local t1 = (-b + math.sqrt(q)) / (2 * a)
  921.         local t2 = (-b - math.sqrt(q)) / (2 * a)
  922.  
  923.         local xt1 = pointA.x * (1 - t1) + t1 * pointB.x
  924.         local yt1 = pointA.y * (1 - t1) + t1 * pointB.y
  925.         local zt1 = pointA.z * (1 - t1) + t1 * pointB.z
  926.  
  927.         local xt2 = pointA.x * (1 - t2) + t2 * pointB.x
  928.         local yt2 = pointA.y * (1 - t2) + t2 * pointB.y
  929.         local zt2 = pointA.z * (1 - t2) + t2 * pointB.z
  930.  
  931.         -- Make sure the intersection is on this line.
  932.         local ipoint1, ipoint2
  933.         if t1 >= 0 and t1 <= 1 then
  934.             table.insert(points, {x = xt1, y = yt1, z = zt1})
  935.         end
  936.  
  937.         -- Make sure the intersection is on this line.
  938.         if t2 >= 0 and t2 <= 1 then
  939.             table.insert(points, {x = xt2, y = yt2, z = zt2})
  940.         end
  941.     end
  942.  
  943.     return points
  944. end
  945.  
  946. function cylinderintersect(cylinder, pointA, pointB)
  947.  
  948.     -- Change x, y and z depending on orientation.
  949.     local x, y, z
  950.  
  951.     if cylinder.o == "z" then
  952.         x, y, z = "x", "y", "z"
  953.     elseif cylinder.o == "x" then
  954.         x, y, z = "z", "y", "x"
  955.     elseif cylinder.o == "y" then
  956.         x, y, z = "x", "z", "y"
  957.     end
  958.  
  959.     local points = {}
  960.  
  961.     -- Determine if line intersects cylinder's edges.
  962.     local a = (pointA[x] - pointB[x]) ^ 2 + (pointA[y] - pointB[y]) ^ 2
  963.     local c = (pointA[x] - cylinder[x]) ^ 2 + (pointA[y] - cylinder[y]) ^ 2 - cylinder.r ^ 2
  964.     local b = (pointB[x] - cylinder[x]) ^ 2 + (pointB[y] - cylinder[y]) ^ 2 - a - c - cylinder.r ^ 2
  965.  
  966.     local q = b ^ 2 - 4 * a * c
  967.  
  968.     if q > 0 then
  969.  
  970.         local t1 = (-b + math.sqrt(q)) / (2 * a)
  971.         local t2 = (-b - math.sqrt(q)) / (2 * a)
  972.  
  973.         local xt1 = pointA[x] * (1 - t1) + t1 * pointB[x]
  974.         local yt1 = pointA[y] * (1 - t1) + t1 * pointB[y]
  975.         local zt1 = pointA[z] * (1 - t1) + t1 * pointB[z]
  976.  
  977.         local xt2 = pointA[x] * (1 - t2) + t2 * pointB[x]
  978.         local yt2 = pointA[y] * (1 - t2) + t2 * pointB[y]
  979.         local zt2 = pointA[z] * (1 - t2) + t2 * pointB[z]
  980.  
  981.         -- Make sure the intersection is on this line and within the cylinder's height.
  982.         if t1 >= 0 and t1 <= 1 and zt1 >= cylinder.hlow and zt1 <= cylinder.hhigh then
  983.             table.insert(points, {[x] = xt1, [y] = yt1, [z] = zt1})
  984.         end
  985.  
  986.         -- Make sure the intersection is on this line and within the cylinder's height.
  987.         if t2 >= 0 and t2 <= 1 and zt2 >= cylinder.hlow and zt2 <= cylinder.hhigh then
  988.             table.insert(points, {[x] = xt2, [y] = yt2, [z] = zt2})
  989.         end
  990.  
  991.         -- Check end caps
  992.         -- If either point on the line is lower than or equal to the lowest point on the cylinder...
  993.         if pointA[z] <= cylinder.hlow or pointB[z] <= cylinder.hlow then
  994.             local tzlow = (cylinder.hlow - pointA[z]) / (pointA[z] + pointB[z])
  995.  
  996.             local xtlow = pointA[x] * (1 - tzlow) + tzlow * pointB[x]
  997.             local ytlow = pointA[y] * (1 - tzlow) + tzlow * pointB[y]
  998.  
  999.             local dist = math.sqrt((cylinder[x] - xtlow) ^ 2 + (cylinder[y] - ytlow) ^ 2)
  1000.  
  1001.             if dist < cylinder.r then
  1002.                 table.insert(points, {[x] = xtlow, [y] = ytlow, [z] = cylinder.hlow})
  1003.             end
  1004.         end
  1005.  
  1006.         -- If either point on the line is higher than or equal to the lowest point on the cylinder...
  1007.         if pointA[z] >= cylinder.hhigh or pointB[z] >= cylinder.hhigh then
  1008.             local tzhigh = (cylinder.hhigh - pointA[z]) / (pointA[z] + pointB[z])
  1009.  
  1010.             local xthigh = pointA[x] * (1 - tzhigh) + tzhigh * pointB[x]
  1011.             local ythigh = pointA[y] * (1 - tzhigh) + tzhigh * pointB[y]
  1012.  
  1013.             local dist = math.sqrt((cylinder[x] - xthigh) ^ 2 + (cylinder[y] - ythigh) ^ 2)
  1014.  
  1015.             if dist < cylinder.r then
  1016.                 table.insert(points, {[x] = xthigh, [y] = ythigh, [z] = cylinder.hhigh})
  1017.             end
  1018.         end
  1019.     end
  1020.  
  1021.     return points
  1022. end
  1023.  
  1024. function rectprismintersect(rectprism, pointA, pointB)
  1025.  
  1026.     local points = {}
  1027.  
  1028.     -- Get the time "t" on the line where intersection with each coordinate's extreme occurs.
  1029.     local txlow = (rectprism.xlow - pointA.x) / (pointA.x + pointB.x)
  1030.     local txhigh = (rectprism.xhigh - pointA.x) / (pointA.x + pointB.x)
  1031.     local tylow = (rectprism.ylow - pointA.y) / (pointA.y + pointB.y)
  1032.     local tyhigh = (rectprism.yhigh - pointA.y) / (pointA.y + pointB.y)
  1033.     local tzlow = (rectprism.zlow - pointA.z) / (pointA.z + pointB.z)
  1034.     local tzhigh = (rectprism.zhigh - pointA.z) / (pointA.z + pointB.z)
  1035.  
  1036.     -- Find other coordinates given t.
  1037.     local xlowy = pointA.y * (1 - txlow) + txlow * pointB.y
  1038.     local xlowz = pointA.z * (1 - txlow) + txlow * pointB.z
  1039.  
  1040.     -- Ensure that this intersection is within the bounds of the rectangular prism.
  1041.     if xlowy <= rectprism.yhigh and xlowy >= rectprism.ylow and xlowz <= rectprism.zhigh and xlowz >= rectprism.zlow then
  1042.         table.insert(points, {x = rectprism.xlow, y = xlowy, z = xlowz})
  1043.     end
  1044.  
  1045.     -- Find other coordinates given t.
  1046.     local xhighy = pointA.y * (1 - txhigh) + txhigh * pointB.y
  1047.     local xhighz = pointA.z * (1 - txhigh) + txhigh * pointB.z
  1048.  
  1049.     -- Ensure that this intersection is within the bounds of the rectangular prism.
  1050.     if xhighy <= rectprism.yhigh and xhighy >= rectprism.ylow and xhighz <= rectprism.zhigh and xhighz >= rectprism.zlow then
  1051.         table.insert(points, {x = rectprism.xhigh, y = xhighy, z = xhighz})
  1052.     end
  1053.  
  1054.     -- Find other coordinates given t.
  1055.     local ylowx = pointA.x * (1 - tylow) + tylow * pointB.x
  1056.     local ylowz = pointA.z * (1 - tylow) + tylow * pointB.z
  1057.  
  1058.     -- Ensure that this intersection is within the bounds of the rectangular prism.
  1059.     if ylowx <= rectprism.xhigh and ylowx >= rectprism.xlow and ylowz <= rectprism.zhigh and ylowz >= rectprism.zlow then
  1060.         table.insert(points, {x = ylowx, y = rectprism.ylow, z = ylowz})
  1061.     end
  1062.  
  1063.     -- Find other coordinates given t.
  1064.     local yhighx = pointA.x * (1 - tyhigh) + tyhigh * pointB.x
  1065.     local yhighz = pointA.z * (1 - tyhigh) + tyhigh * pointB.z
  1066.  
  1067.     -- Ensure that this intersection is within the bounds of the rectangular prism.
  1068.     if yhighx <= rectprism.xhigh and yhighx >= rectprism.xlow and yhighz <= rectprism.zhigh and yhighz >= rectprism.zlow then
  1069.         table.insert(points, {x = yhighx, y = rectprism.yhigh, z = yhighz})
  1070.     end
  1071.  
  1072.     -- Find other coordinates given t.
  1073.     local zlowx = pointA.x * (1 - tzlow) + tzlow * pointB.x
  1074.     local zlowy = pointA.y * (1 - tzlow) + tzlow * pointB.y
  1075.  
  1076.     -- Ensure that this intersection is within the bounds of the rectangular prism.
  1077.     if zlowx <= rectprism.xhigh and zlowx >= rectprism.xlow and zlowy <= rectprism.yhigh and zlowy >= rectprism.ylow then
  1078.         table.insert(points, {x = zlowx, y = zlowy, z = rectprism.zlow})
  1079.     end
  1080.  
  1081.     -- Find other coordinates given t.
  1082.     local zhighx = pointA.x * (1 - tzhigh) + tzhigh * pointB.x
  1083.     local zhighy = pointA.y * (1 - tzhigh) + tzhigh * pointB.y
  1084.  
  1085.     -- Ensure that this intersection is within the bounds of the rectangular prism.
  1086.     if zhighx <= rectprism.xhigh and zhighx >= rectprism.xlow and zhighy <= rectprism.yhigh and zhighy >= rectprism.ylow then
  1087.         table.insert(points, {x = zhighx, y = zhighy, z = rectprism.zhigh})
  1088.     end
  1089.  
  1090.     -- Corner and edge check
  1091.     if #points > 2 then
  1092.         -- Remove entries from the table while we loop through it.
  1093.         local i = 0
  1094.         while i <= #points do
  1095.             -- There can be a total of three copies of the same coordinates (if the intersection happens at a corner).
  1096.             local rem1, rem2
  1097.             for k,v in ipairs(points) do
  1098.                 if points[i].x == v.x and points[i].y == v.y and points[i].z == v.z then
  1099.                     if rem1 then
  1100.                         rem2 = i - 1  -- Subtract 1 from the key since rem1 will be removed first and this key will be lowered by 1 when that happens.
  1101.                     else
  1102.                         rem1 = i
  1103.                     end
  1104.                 end
  1105.             end
  1106.  
  1107.             -- Remove the two keys.
  1108.             if rem1 or rem2 then
  1109.                 if rem1 then
  1110.                     table.remove(points, rem1)
  1111.                 elseif rem2 then
  1112.                     table.remove(points, rem2)
  1113.                 end
  1114.             else
  1115.                 i = i + 1  -- Increment i only if an entry has not been deleted.
  1116.             end
  1117.         end
  1118.     end
  1119.  
  1120.     return points
  1121. end
  1122.  
  1123. function circleintersect(circle, pointA, pointB)
  1124.  
  1125.     -- Change x, y and z depending on orientation.
  1126.     local x, y, z
  1127.  
  1128.     if circle.o == "z" then
  1129.         x, y, z = "x", "y", "z"
  1130.     elseif circle.o == "x" then
  1131.         x, y, z = "z", "y", "x"
  1132.     elseif circle.o == "y" then
  1133.         x, y, z = "x", "z", "y"
  1134.     end
  1135.  
  1136.     local points = {}
  1137.  
  1138.     -- Find the time "t" when the line intersects the plane at which the circle is oriented.
  1139.     local tz = (circle[z] - pointA[z]) / (pointA[z] + pointB[z])
  1140.     local xtz = pointA[x] * (1 - tz) + tz * pointB[x]
  1141.     local ytz = pointA[y] * (1 - tz) + tz * pointB[y]
  1142.  
  1143.     -- Find the distance from this intersection point to the center of the circle and compare it against the radius.
  1144.     local dist = math.sqrt((xtz - circle[x]) ^ 2 + (ytz - circle[y]) ^ 2)
  1145.     if dist <= circle.r then
  1146.         table.insert(points, {[x] = xtz, [y] = ytz, [z] = circle[z]})
  1147.     end
  1148.  
  1149.     return points
  1150. end
  1151.  
  1152. function rectangleintersect(rectangle, pointA, pointB)
  1153.  
  1154.     -- Change x, y and z depending on orientation.
  1155.     local x, y, z
  1156.  
  1157.     if rectangle.o == "z" then
  1158.         x, y, z = "x", "y", "z"
  1159.     elseif rectangle.o == "x" then
  1160.         x, y, z = "z", "y", "x"
  1161.     elseif rectangle.o == "y" then
  1162.         x, y, z = "x", "z", "y"
  1163.     end
  1164.  
  1165.     local points = {}
  1166.    
  1167.     -- Find the time "t" when the line intersects the plane at which the rectangle is oriented.
  1168.     local tz = (rectangle[z] - pointA[z]) / (pointA[z] + pointB[z])
  1169.     local xtz = pointA[x] * (1 - tz) + tz * pointB[x]
  1170.     local ytz = pointA[y] * (1 - tz) + tz * pointB[y]
  1171.  
  1172.     -- Find if the intersection point is within the bounds of the rectangle.
  1173.     if xtz >= rectangle[x .. "low"] and xtz <= rectangle[x .. "high"] and ytz >= rectangle[y .. "low"] and ytz <= rectangle[y .. "high"] then
  1174.         table.insert(points, {[x] = xtz, [y] = ytz, [z] = rectangle[z]})
  1175.     end
  1176.  
  1177.     return points
  1178. end
  1179.  
  1180. -- Object Creation Comments (refer to this function for questions on how the other ones work)
  1181. function GEO.newSphere(name, r, x, y, z)
  1182.  
  1183.     -- Check to see if there is already a GEO with this name.
  1184.     if not GEO[name] then
  1185.         -- Create new table
  1186.         GEO[name] = {}
  1187.         GEO[name].t = "sphere"  -- type
  1188.         GEO[name].n = name  -- name
  1189.         GEO[name].r = r or 1  -- radius (default value of 1)
  1190.         GEO[name].x = x or 0  -- x coordinate of center (default value of 0)
  1191.         GEO[name].y = y or 0  -- y coordinate of center (default value of 0)
  1192.         GEO[name].z = z or 0  -- z coordinate of center (default value of 0)
  1193.            
  1194.         -- Initialize monitor and contain tables
  1195.         GEO[name].m = {}
  1196.         GEO[name].c = {}
  1197.            
  1198.         -- Initialize list of antigeos
  1199.         GEO[name].anti = {}
  1200.            
  1201.         -- Notify the console that a sphere has been created.
  1202.         hprintf("Sphere \"" .. name .. "\" created.")
  1203.         setmetatable(GEO[name], GEO)  -- Add this object to the GEO metatable to allow GEO-editing functions to work on it.
  1204.         return GEO[name]  -- Return the object
  1205.     end
  1206.        
  1207.     -- If no object was returned, the name was invalid; notify the console.
  1208.     hprintf("Invalid name: \"" .. name .. "\"")
  1209. end
  1210.  
  1211. function GEO.newCircle(name, r, x, y, z, orientation)
  1212.  
  1213.     if not GEO[name] then
  1214.         GEO[name] = {}
  1215.         GEO[name].t = "circle"
  1216.         GEO[name].n = name
  1217.         GEO[name].o = orientation or "z"  -- orientation
  1218.         GEO[name].r = r or 0
  1219.         GEO[name].x = x or 0
  1220.         GEO[name].y = y or 0
  1221.         GEO[name].z = z or 0
  1222.            
  1223.         -- Initialize monitor and contain tables
  1224.         GEO[name].m = {}
  1225.         GEO[name].c = {}
  1226.            
  1227.         -- Initialize list of antigeos
  1228.         GEO[name].anti = {}
  1229.            
  1230.         hprintf("Circle \"" .. name .. "\" created.")
  1231.         setmetatable(GEO[name], GEO)
  1232.         return GEO[name]
  1233.     end
  1234.        
  1235.     hprintf("Invalid name: \"" .. name .. "\"")
  1236. end
  1237.  
  1238. function GEO.newCylinder(name, r, h, x, y, z, orientation)
  1239.  
  1240.     if not GEO[name] then
  1241.         GEO[name] = {}
  1242.         GEO[name].t = "cylinder"
  1243.         GEO[name].n = name
  1244.         x = x or 0
  1245.         y = y or 0
  1246.         z = z or 0
  1247.         r = r or 1
  1248.         h = h or 1
  1249.         orientation = orientation or "z"
  1250.         GEO[name].x = x
  1251.         GEO[name].y = y
  1252.         GEO[name].z = z
  1253.         GEO[name].r = r
  1254.         GEO[name].h = h  -- height
  1255.         GEO[name].o = orientation  -- orientation "x", "y", or "z"
  1256.         GEO[name].hlow = GEO[name][orientation] - h / 2  -- lowest orientation coordinate still within the cylinder
  1257.         GEO[name].hhigh = GEO[name][orientation] + h / 2  -- highest orientation coordinate still within the cylinder
  1258.            
  1259.         -- Initialize monitor and contain tables
  1260.         GEO[name].m = {}
  1261.         GEO[name].c = {}
  1262.            
  1263.         -- Initialize list of antigeos
  1264.         GEO[name].anti = {}
  1265.            
  1266.         hprintf("Cylinder \"" .. name .. "\" created.")
  1267.         setmetatable(GEO[name], GEO)
  1268.         return GEO[name]
  1269.     end
  1270. end
  1271.  
  1272. function GEO.newRectPrism(name, lx, ly, lz, x, y, z)
  1273.  
  1274.     if not GEO[name] then
  1275.         GEO[name] = {}
  1276.         GEO[name].t = "rectprism"
  1277.         GEO[name].n = name
  1278.         lx = lx or 1
  1279.         ly = ly or 1
  1280.         lz = lz or 1
  1281.         x = x or 0
  1282.         y = y or 0
  1283.         z = z or 0
  1284.         GEO[name].lx = lx  -- x length
  1285.         GEO[name].ly = ly  -- y length
  1286.         GEO[name].lz = lz  -- z length
  1287.         GEO[name].x = x
  1288.         GEO[name].y = y
  1289.         GEO[name].z = z
  1290.         GEO[name].xlow = x - lx / 2  -- lowest x-coordinate still within the rectangular prism
  1291.         GEO[name].xhigh = lx / 2 + x  -- highest x-coordinate still within in the rectangular prism
  1292.         GEO[name].ylow = y - ly / 2  -- lowest y-coordinate still within the rectangular prism
  1293.         GEO[name].yhigh = ly / 2 + y  -- highest y-coordinate still within the rectangular prism
  1294.         GEO[name].zlow = z - lz / 2  -- lowest z-coordinate still within the rectangular prism
  1295.         GEO[name].zhigh = lz / 2 + z  -- highest z-coordinate still within the rectangular prism
  1296.            
  1297.         -- Initialize monitor and contain tables
  1298.         GEO[name].m = {}
  1299.         GEO[name].c = {}
  1300.            
  1301.         -- Initialize list of antigeos
  1302.         GEO[name].anti = {}
  1303.            
  1304.         hprintf("Rectangular Prism \"" .. name .. "\" created.")
  1305.         setmetatable(GEO[name], GEO)
  1306.         return GEO[name]
  1307.     end
  1308.        
  1309.     hprintf("Invalid name: \"" .. name .. "\"")
  1310. end
  1311.  
  1312. function GEO.newRect(name, width, height, x, y, z, orientation)
  1313.  
  1314.     if not GEO[name] then
  1315.         GEO[name] = {}
  1316.         GEO[name].t = "rectangle"
  1317.         GEO[name].n = name
  1318.         width = width or 1
  1319.         height = height or 1
  1320.         x = x or 0
  1321.         y = y or 0
  1322.         z = z or 0
  1323.         orientation = orientation or "z"
  1324.         GEO[name].width = width
  1325.         GEO[name].height = height
  1326.         GEO[name].x = x
  1327.         GEO[name].y = y
  1328.         GEO[name].z = z
  1329.         GEO[name].o = orientation
  1330.            
  1331.         -- Coordinates' highs and lows depend on orientation
  1332.         if orientation == "z" then
  1333.             GEO[name].xlow = x - width / 2
  1334.             GEO[name].xhigh = x + width / 2
  1335.             GEO[name].ylow = y - height / 2
  1336.             GEO[name].yhigh = y + height / 2
  1337.             GEO[name].zlow = z
  1338.             GEO[name].zhigh = z
  1339.         elseif orientation == "x" then
  1340.             GEO[name].xlow = x
  1341.             GEO[name].xhigh = x
  1342.             GEO[name].ylow = y - width / 2
  1343.             GEO[name].yhigh = y + width / 2
  1344.             GEO[name].zlow = z - height / 2
  1345.             GEO[name].zhigh = z + height / 2
  1346.         elseif orientation == "y" then
  1347.             GEO[name].xlow = x - width / 2
  1348.             GEO[name].xhigh = x + width / 2
  1349.             GEO[name].ylow = y
  1350.             GEO[name].yhigh = y
  1351.             GEO[name].zlow = z - height / 2
  1352.             GEO[name].zhigh = z + height / 2
  1353.         end
  1354.            
  1355.         -- Initialize monitor and contain tables
  1356.         GEO[name].m = {}
  1357.         GEO[name].c = {}
  1358.            
  1359.         -- Initialize list of antigeos
  1360.         GEO[name].anti = {}
  1361.            
  1362.         hprintf("Rectangle \"" .. name .. "\" created.")
  1363.         setmetatable(GEO[name], GEO)
  1364.         return GEO[name]
  1365.     end
  1366.        
  1367.     hprintf("Invalid name: \""  .. name .. "\"")
  1368. end
  1369.  
  1370. function GEO.get(name)
  1371.  
  1372.     return GEO[name]
  1373. end
  1374.  
  1375. function GEO.clear()
  1376.  
  1377.     for k,v in pairs(GEO) do
  1378.         if type(v) == "table" and k ~= "__index" then  -- make sure we're actually getting a GEO object
  1379.             v:delete()  -- Delete every GEO
  1380.         end
  1381.     end
  1382. end
  1383.  
  1384. function GEO:delete()
  1385.  
  1386.     -- Get rid of perimeter and surface objects
  1387.     self:hide()
  1388.        
  1389.     -- Make sure any GEO that this is an AntiGEO of is aware that this GEO no longer exists
  1390.     for k,v in pairs(GEO) do
  1391.         if type(v) == "table" and k ~= "__index" then
  1392.             for name,bool in pairs(v.anti) do
  1393.                 if name == self.n then
  1394.                     GEO[v.n].anti[name] = nil
  1395.                     break
  1396.                 end
  1397.             end
  1398.         end
  1399.     end
  1400.        
  1401.     -- Nullify GEO
  1402.     GEO[self.n] = nil
  1403.     hprintf("Geo \"" .. self.n .. "\" deleted.")
  1404. end
  1405.  
  1406. function GEO:move(x, y, z)
  1407.  
  1408.     -- Move the center of the object
  1409.     -- Default to GEO's current coordinates
  1410.     GEO[self.n].x = x or GEO[self.n].x
  1411.     GEO[self.n].y = y or GEO[self.n].y
  1412.     GEO[self.n].z = z or GEO[self.n].z
  1413.        
  1414.     -- If this is a rectangular prism...
  1415.     if self.t == "rectprism" then
  1416.         -- Change the x, y, and z lows and highs accordingly to adjust to the new center
  1417.         GEO[self.n].xlow = x - GEO[self.n].lx / 2  
  1418.         GEO[self.n].xhigh = GEO[self.n].lx / 2 + x
  1419.         GEO[self.n].ylow = y - GEO[self.n].ly / 2
  1420.         GEO[self.n].yhigh = GEO[self.n].ly / 2 + y
  1421.         GEO[self.n].zlow = z - GEO[self.n].lz / 2
  1422.         GEO[self.n].zhigh = GEO[self.n].lz / 2 + z
  1423.        
  1424.     -- If this is a rectangle...
  1425.     elseif self.t == "rectangle" then
  1426.         -- Change the x, y, and z lows and highs accordingly to adjust to the new center (depends on orientation)
  1427.         if self.o == "z" then
  1428.             GEO[self.n].xlow = self.x - self.width / 2
  1429.             GEO[self.n].xhigh = self.x + self.width / 2
  1430.             GEO[self.n].ylow = self.y - self.height / 2
  1431.             GEO[self.n].yhigh = self.y + self.height / 2
  1432.             GEO[self.n].zlow = self.z
  1433.             GEO[self.n].zhigh = self.z
  1434.         elseif self.o == "x" then
  1435.             GEO[self.n].xlow = self.x
  1436.             GEO[self.n].xhigh = self.x
  1437.             GEO[self.n].ylow = self.y - self.width / 2
  1438.             GEO[self.n].yhigh = self.y + self.width / 2
  1439.             GEO[self.n].zlow = self.z - self.height / 2
  1440.             GEO[self.n].zhigh = self.z + self.height / 2
  1441.         elseif self.o == "y" then
  1442.             GEO[self.n].xlow = self.x - self.width / 2
  1443.             GEO[self.n].xhigh = self.x + self.width / 2
  1444.             GEO[self.n].ylow = self.y
  1445.             GEO[self.n].yhigh = self.y
  1446.             GEO[self.n].zlow = self.z - self.height / 2
  1447.             GEO[self.n].zhigh = self.z + self.height / 2
  1448.         end
  1449.        
  1450.     -- If this is a cylinder...
  1451.     elseif self.t == "cylinder" then
  1452.         GEO[self.n].hlow = self[self.o] - self.h / 2
  1453.         GEO[self.n].hhigh = self[self.o] + self.h / 2
  1454.     end
  1455. end
  1456.  
  1457. function GEO:radius(new)
  1458.  
  1459.     if self.t == "sphere" or self.t == "circle" or self.t == "cylinder" then
  1460.         if new then  -- If "new" is defined...
  1461.             GEO[self.n].r = new  -- Change the radius to its value.
  1462.         else  -- If not...
  1463.             return GEO[self.n].r  -- Return its current radius.
  1464.         end
  1465.     end
  1466. end
  1467.  
  1468. function GEO:size(x, y, z)
  1469.  
  1470.     -- If this is a rectangular prism...
  1471.     if self.t == "rectprism" then
  1472.         if x or y or z then  -- If any of these variables have been defined...
  1473.             -- Adjust lengths and x, y, and z highs and lows accordingly.
  1474.             GEO[self.n].lx = x or GEO[self.n].lx
  1475.             GEO[self.n].ly = y or GEO[self.n].ly
  1476.             GEO[self.n].lz = z or GEO[self.n].lz
  1477.             GEO[self.n].xlow = GEO[self.n].x - x / 2
  1478.             GEO[self.n].xhigh = x / 2 + GEO[self.n].x
  1479.             GEO[self.n].ylow = GEO[self.n].y - y / 2
  1480.             GEO[self.n].yhigh = y / 2 + GEO[self.n].y
  1481.             GEO[self.n].zlow = GEO[self.n].z - z / 2
  1482.             GEO[self.n].zhigh = z / 2 + GEO[self.n].z
  1483.         else  -- Otherwise...
  1484.             return GEO[self.n].lx, GEO[self.n].ly, GEO[self.n].lz  -- Return the x, y, and z lengths.
  1485.         end
  1486.        
  1487.     -- If this is a rectangle...
  1488.     elseif self.t == "rectangle" then
  1489.         if x or y or z then  -- If any of these variables are defined...
  1490.             -- Adjust width, height, and x, y, and z highs and lows accordingly (depends on orientation).
  1491.             if self.o == "z" then
  1492.                 GEO[self.n].width = x
  1493.                 GEO[self.n].height = y
  1494.                 GEO[self.n].xlow = self.x - self.width / 2
  1495.                 GEO[self.n].xhigh = self.x + self.width / 2
  1496.                 GEO[self.n].ylow = self.y - self.height / 2
  1497.                 GEO[self.n].yhigh = self.y + self.height / 2
  1498.                 GEO[self.n].zlow = self.z
  1499.                 GEO[self.n].zhigh = self.z
  1500.             elseif self.o == "x" then
  1501.                 GEO[self.n].width = y
  1502.                 GEO[self.n].height = z
  1503.                 GEO[self.n].xlow = self.x
  1504.                 GEO[self.n].xhigh = self.x
  1505.                 GEO[self.n].ylow = self.y - self.width / 2
  1506.                 GEO[self.n].yhigh = self.y + self.width / 2
  1507.                 GEO[self.n].zlow = self.z - self.height / 2
  1508.                 GEO[self.n].zhigh = self.z + self.height / 2
  1509.             elseif self.o == "y" then
  1510.                 GEO[self.n].width = x
  1511.                 GEO[self.n].height = z
  1512.                 GEO[self.n].xlow = self.x - self.width / 2
  1513.                 GEO[self.n].xhigh = self.x + self.width / 2
  1514.                 GEO[self.n].ylow = self.y
  1515.                 GEO[self.n].yhigh = self.y
  1516.                 GEO[self.n].zlow = self.z - self.height / 2
  1517.                 GEO[self.n].zhigh = self.z + self.height / 2
  1518.             end
  1519.         else  -- Otherwise...
  1520.             return GEO[self.n].width, GEO[self.n].height  -- Return the width and height of the rectangle.
  1521.         end
  1522.        
  1523.     -- If this is a cylinder...
  1524.     elseif self.t == "cylinder" then
  1525.         local h = x or y or z  -- Whichever variable is defined, it is taken as the height.
  1526.         if h then  -- If a height is specified...
  1527.             -- Adjust height and z high and low accordingly.
  1528.             GEO[self.n].h = h
  1529.             GEO[self.n].hlow = self[self.o] - h / 2
  1530.             GEO[self.n].hhigh = self[self.o] + h / 2
  1531.         else  -- Otherwise...
  1532.             return GEO[self.n].h  -- Return the height.
  1533.         end
  1534.     end
  1535. end
  1536.  
  1537. function GEO:extend(orientation, direction, amount)
  1538.  
  1539.     -- Change the direction from "+" or "-" to "high" or "low".
  1540.     local dir
  1541.     dir = string.gsub(direction, "-", "low")
  1542.     dir = string.gsub(direction, "+", "high")
  1543.        
  1544.     -- Get the face we're trying to extend (i.e. "xhigh")
  1545.     local face = string.lower(orientation) .. direction
  1546.        
  1547.     -- If this is a rectangular prism or a rectangle...
  1548.     if self.t == "rectprism" or self.t == "rectangle" then
  1549.         -- Make sure "face" is actually a valid face (and not something like "cheesederp")
  1550.         if self[face] then
  1551.             -- Use "GEO[self.n]" when you want to actually permanently change the value of something within the object; use "self" for reading information from the object.
  1552.             -- Change the length of the GEO in the orientation specified.
  1553.             GEO[self.n]["l" .. string.lower(orientation)] = self["l" .. string.lower(orientation)] + amount
  1554.                
  1555.             -- Figure out if the positive or negative face is being extended.
  1556.             if direction == "+" then
  1557.                 GEO[self.n][face] = self[face] + amount
  1558.                 GEO[self.n][string.lower(orientation)] = self[string.lower(orientation)] + amount / 2
  1559.             else
  1560.                 GEO[self.n][face] = self[face] - amount
  1561.                 GEO[self.n][string.lower(orientation)] = self[string.lower(orientation)] - amount / 2
  1562.             end
  1563.         end
  1564.        
  1565.     -- If this is a cylinder...
  1566.     elseif self.t == "cylinder" then
  1567.         -- The orientation must be the orientation of the cylinder
  1568.         if orientation == self.o then
  1569.             if self[face] then
  1570.                 GEO[self.n].h = self.h + amount
  1571.                    
  1572.                 -- Figure out if the top or bottom face is being extended.
  1573.                 if direction == "+" then
  1574.                     GEO[self.n].hhigh = self.hhigh + amount
  1575.                     GEO[self.n][self.o] = self[self.o] + amount / 2
  1576.                 else
  1577.                     GEO[self.n].hhigh = self.hhigh - amount
  1578.                     GEO[self.n][self.o] = self[self.o] - amount / 2
  1579.                 end
  1580.             end
  1581.         end
  1582.     end
  1583. end
  1584.  
  1585. function GEO:antigeo(boolean, ...)
  1586.  
  1587.     GEO[self.n].geolist = GEO[self.n].geolist or {}
  1588.  
  1589.     -- Get all GEOs from args.
  1590.     local geos = {...}
  1591.        
  1592.     if boolean == true then
  1593.         for _,geo in ipairs(geos) do
  1594.             local name = geo:name()
  1595.             -- Insert AntiGEO into the specified GEOs' AntiGEO table.
  1596.             GEO[name].anti[self.n] = true
  1597.             -- Insert GEO into list of AntiGEO's GEOs.
  1598.             GEO[self.n].geolist[name] = true
  1599.         end
  1600.     elseif boolean == false then
  1601.         for _,geo in ipairs(geos) do
  1602.             local name = geo:name()
  1603.             -- Remove AntiGEO from table.
  1604.             GEO[name].anti[self.n] = false
  1605.             -- Remove GEO from AntiGEO table.
  1606.             GEO[self.n].geolist[name] = false
  1607.         end
  1608.     elseif boolean == nil then
  1609.         if GEO[self.n].a == true then
  1610.             return true
  1611.         else
  1612.             return false
  1613.         end
  1614.     end
  1615.        
  1616.     local count = 0
  1617.     for k,v in pairs(GEO[self.n].geolist) do
  1618.         count = count + 1
  1619.     end
  1620.        
  1621.     if count > 0 then
  1622.         GEO[self.n].a = true
  1623.     else
  1624.         GEO[self.n].a = false
  1625.     end
  1626. end
  1627.  
  1628. function GEO:coords()
  1629.  
  1630.     return self.x, self.y, self.z
  1631. end
  1632.  
  1633. function GEO:high(orientation)
  1634.  
  1635.     if self.t == "sphere" or self.t == "circle" then
  1636.         return self[orientation] + self.r
  1637.     elseif self.t == "rectprism" or self.t == "rectangle" then
  1638.         return self[orientation .. "high"]
  1639.     elseif self.t == "cylinder" then
  1640.         if orientation == self.o then
  1641.             return self.hhigh
  1642.         else
  1643.             return self[orientation] + self.r
  1644.         end
  1645.     end        
  1646. end
  1647.  
  1648. function GEO:low(orientation)
  1649.  
  1650.     if self.t == "sphere" or self.t == "circle" then
  1651.         return self[orientation] - self.r
  1652.     elseif self.t == "rectprism" or self.t == "rectangle" then
  1653.         return self[orientation .. "low"]
  1654.     elseif self.t == "cylinder" then
  1655.         if orientation == self.o then
  1656.             return self.hlow
  1657.         else
  1658.             return self[orientation] - self.r
  1659.         end
  1660.     end        
  1661. end
  1662.  
  1663. function GEO:randomcoords()
  1664.  
  1665.     if self.t == "sphere" then
  1666.         return randomInSphere(self.x, self.y, self.z, self.r)
  1667.     elseif self.t == "circle" then
  1668.         return randomInCircle(self.x, self.y, self.z, self.r, self.o)
  1669.     elseif self.t == "cylinder" then
  1670.         return randomInCylinder(self.x, self.y, self.z, self.hlow, self.hhigh, self.r, self.o)
  1671.     elseif self.t == "rectprism" or self.t == "rectangle" then
  1672.         return randomInRectPrism(self.xlow, self.xhigh, self.ylow, self.yhigh, self.zlow, self.zhigh)
  1673.     end
  1674. end
  1675.  
  1676. function GEO:type()
  1677.  
  1678.     return self.t
  1679. end
  1680.  
  1681. function GEO:orientation()
  1682.  
  1683.     return self.o
  1684. end
  1685.  
  1686. function GEO:name()
  1687.  
  1688.     return self.n
  1689. end
  1690.  
  1691. function GEO:perimeter(density, mapId)
  1692.        
  1693.     -- Default density to 10
  1694.     density = density or 10
  1695.        
  1696.     -- Default tagtype and tagname to Full-Spectrum Visions
  1697.     mapId = mapId or gettagid("eqip", "powerups\\full-spectrum vision")
  1698.        
  1699.     tagname, tagtype = gettaginfo(mapId)
  1700.        
  1701.     -- Store all of the perimeter objects in a table
  1702.     GEO[self.n].p = GEO[self.n].p or {}
  1703.        
  1704.     -- Find the change in angle per point from 0 - 2pi (0° - 360°)
  1705.     local angle_increment = 2 * math.pi / density
  1706.     if self.t == "sphere" then
  1707.         for i = 1,density do
  1708.             -- Use trigonometry to find the outer edge of the circle (for a sphere, this will be at the z-center -- the widest part of the sphere).
  1709.             local x = self.r * math.cos(angle_increment * i)
  1710.             local y = self.r * math.sin(angle_increment * i)
  1711.             local z = self.z
  1712.             local objId = createobject(mapId, 0, nil, false, self.x + x, self.y + y, self.z)
  1713.             GEO[self.n].p[objId] = {self.x + x, self.y + y, self.z}
  1714.         end
  1715.     elseif self.t == "circle" or self.t == "cylinder" then
  1716.         if self.o == "z" then
  1717.             for i = 1,density do
  1718.                 -- Use trigonometry to find the outer edge of the circle.
  1719.                 local x = self.r * math.cos(angle_increment * i)
  1720.                 local y = self.r * math.sin(angle_increment * i)
  1721.                 local z = self.z
  1722.                 local objId = createobject(mapId, 0, nil, false, self.x + x, self.y + y, self.z)
  1723.                 GEO[self.n].p[objId] = {self.x + x, self.y + y, self.z}
  1724.             end
  1725.         elseif self.o == "x" then
  1726.             for i = 1,density do
  1727.                 -- Use trigonometry to find the outer edge of the circle.
  1728.                 local x = self.x
  1729.                 local y = self.r * math.cos(angle_increment * i)
  1730.                 local z = self.r * math.sin(angle_increment * i)
  1731.                 local objId = createobject(mapId, 0, nil, false, self.x, self.y + y, self.z + z)
  1732.                 GEO[self.n].p[objId] = {self.x, self.y + y, self.z}
  1733.             end
  1734.         elseif self.o == "y" then
  1735.             for i = 1,density do
  1736.                 -- Use trigonometry to find the outer edge of the circle.
  1737.                 local x = self.r * math.cos(angle_increment * i)
  1738.                 local y = self.y
  1739.                 local z = self.r * math.sin(angle_increment * i)
  1740.                 hprintf("x: " .. (self.x + x) .. " y: " .. (self.y) .. " z: " .. (self.z + z))
  1741.                 local objId = createobject(mapId, 0, nil, false, self.x + x, self.y, self.z + z)
  1742.                 GEO[self.n].p[objId] = {self.x + x, self.y, self.z + z}
  1743.             end
  1744.         end
  1745.     elseif self.t == "rectprism" or self.t == "rectangle" then
  1746.         if self.t == "rectangle" then
  1747.             if self.o ~= "z" then return end
  1748.         end
  1749.         -- Create points at four corners of the rectangle
  1750.         local o1 = createobject(mapId, 0, nil, false, self.xhigh, self.yhigh, self.z)
  1751.         local o2 = createobject(mapId, 0, nil, false, self.xhigh, self.ylow, self.z)
  1752.         local o3 = createobject(mapId, 0, nil, false, self.xlow, self.yhigh, self.z)
  1753.         local o4 = createobject(mapId, 0, nil, false, self.xlow, self.ylow, self.z)
  1754.         GEO[self.n].p[o1] = {self.xhigh, self.yhigh, self.z}
  1755.         GEO[self.n].p[o2] = {self.xhigh, self.yhigh, self.z}
  1756.         GEO[self.n].p[o3] = {self.xhigh, self.yhigh, self.z}
  1757.         GEO[self.n].p[o4] = {self.xhigh, self.yhigh, self.z}
  1758.            
  1759.         for i = 1,density do
  1760.             local herp = createobject(mapId, 0, nil, false, self.xhigh - (i * self.lx / density), self.yhigh, self.z)
  1761.             local derp = createobject(mapId, 0, nil, false, self.xhigh, self.yhigh - (i * self.ly / density), self.z)
  1762.             local weee = createobject(mapId, 0, nil, false, self.xhigh - (i * self.lx / density), self.ylow, self.z)
  1763.             local cheese = createobject(mapId, 0, nil, false, self.xlow, self.ylow + (i * self.ly / density), self.z)
  1764.             GEO[self.n].p[herp] = {self.xhigh - (i * self.lx / density), self.yhigh, self.z}
  1765.             GEO[self.n].p[derp] = {self.xhigh, self.yhigh - (i * self.ly / density), self.z}
  1766.             GEO[self.n].p[weee] = {self.xhigh - (i * self.lx / density), self.ylow, self.z}
  1767.             GEO[self.n].p[cheese] = {self.xlow, self.ylow + (i * self.ly / density), self.z}
  1768.         end
  1769.     end
  1770.        
  1771.     for objId,_ in pairs(GEO[self.n].p) do
  1772.         writebit(getobject(objId) + 0x10, 5, true)  -- Ignore pyhsics
  1773.     end
  1774.        
  1775.     -- Also show the surface or perimeter of AntiGEOs with Overshield powerups
  1776.     for name,bool in pairs(GEO[self.n].anti) do
  1777.         if bool then
  1778.             local geo = GEO.get(name)
  1779.             if geo:type() == "sphere" or geo:type() == "cylinder" or geo:type() == "rectprism" then
  1780.                 geo:surface(density, gettagid("eqip", "powerups\\over shield"))
  1781.             else
  1782.                 geo:perimeter(density, gettagid("eqip", "powerups\\over shield"))
  1783.             end
  1784.         end
  1785.     end
  1786. end
  1787.  
  1788. function GEO:surface(density, mapId)
  1789.  
  1790.     -- Default density to 12
  1791.     density = density or 12
  1792.    
  1793.     -- Default tagtype and tagname to Full-Spectrum Visions
  1794.     mapId = mapId or gettagid("eqip", "powerups\\full-spectrum vision")
  1795.  
  1796.     -- Store all perimeter objects in a table
  1797.     GEO[self.n].p = GEO[self.n].p or {}
  1798.    
  1799.     -- Store all of the objects we will create
  1800.     local objects = {}
  1801.    
  1802.     if self.t == "sphere" then
  1803.        
  1804.         -- Use concentric rings based on the density given to simulate a sphere
  1805.         local laterals = density
  1806.         local latdist = self.r * 2 / laterals
  1807.         local curz = self.z + self.r
  1808.         local r = 0
  1809.         for i = 1, laterals do
  1810.             if i <= math.floor(laterals / 2) then
  1811.                 local d = i / math.floor(laterals / 2) * density
  1812.                 -- Find the change in angle per point from 0 - 2pi (0° - 360°)
  1813.                 local angle_increment = 2 * math.pi / density
  1814.                 for j = 1, density do
  1815.                     -- Use trigonometry to find the outer edge of the circle (for a sphere, this will be at the z-center -- the widest part of the sphere).
  1816.                     local x = r * math.cos((angle_increment) * j)
  1817.                     local y = r * math.sin((angle_increment) * j)
  1818.                     local z = curz
  1819.                     table.insert(objects, {self.x + x, self.y + y, z})
  1820.                 end
  1821.                
  1822.             elseif i >= math.ceil(laterals / 2) then
  1823.                 local d = math.floor(laterals / 2) / i * density
  1824.                 -- Find the change in angle per point from 0 - 2pi (0° - 360°)
  1825.                 local angle_increment = 2 * math.pi / density      
  1826.                 for j = 1, density do
  1827.                     -- Use trigonometry to find the outer edge of the circle (for a sphere, this will be at the z-center -- the widest part of the sphere).
  1828.                     local x = r * math.cos((angle_increment) * j)
  1829.                     local y = r * math.sin((angle_increment) * j)
  1830.                     local z = curz
  1831.                     table.insert(objects, {self.x + x, self.y + y, z})
  1832.                 end
  1833.             end
  1834.            
  1835.             -- Lower the lateral
  1836.             curz = curz - latdist
  1837.             local diff = curz - self.z
  1838.             local angle = math.acos(math.abs(diff) / self.r)
  1839.            
  1840.             -- Get the new radius at this sphere cap
  1841.             r = math.sin(angle) * self.r
  1842.         end
  1843.        
  1844.     elseif self.t == "cylinder" then
  1845.    
  1846.         -- Use multiple rings based on density given to simulate cylinder
  1847.         local laterals = math.ceil(density / 2)
  1848.         local latdist = self.h / laterals
  1849.         local curh = self[self.o] + (self.h / 2)
  1850.        
  1851.         for i = 0, laterals do
  1852.             if i == 0 or i == laterals then
  1853.                 local rings = density / 2
  1854.                 local inc = self.r / rings
  1855.                 local radius = self.r - inc
  1856.                 for x = 1, rings - 1 do
  1857.                     for j = 1, density do
  1858.                         if self.o == "z" then
  1859.                             local angle_increment = 2 * math.pi / density
  1860.                             local x = radius * math.cos(angle_increment * j)
  1861.                             local y = radius * math.sin(angle_increment * j)
  1862.                             table.insert(objects, {self.x + x, self.y + y, curh})
  1863.                         elseif self.o == "x" then
  1864.                             local y = radius * math.cos(angle_increment * j)
  1865.                             local z = radius * math.sin(angle_increment * j)
  1866.                             table.insert(objects, {curh, self.y + y, self.z + z})
  1867.                         elseif self.o == "y" then
  1868.                             local x = radius * math.cos(angle_increment * j)
  1869.                             local z = radius * math.sin(angle_increment * j)
  1870.                             table.insert(objects, {self.x + x, curh, self.z + z})
  1871.                         end
  1872.                     end
  1873.                    
  1874.                     radius = radius - inc
  1875.                 end
  1876.             end
  1877.            
  1878.             for j = 1, density do
  1879.                 -- Find the change in angle per point from 0 - 2pi (0° - 360°)
  1880.                 local angle_increment = 2 * math.pi / density
  1881.                 -- Use trigonometry to find the outer edge of the circle.
  1882.                 local x, y, z
  1883.                 if self.o == "z" then
  1884.                     x = self.r * math.cos(angle_increment * j)
  1885.                     y = self.r * math.sin(angle_increment * j)
  1886.                     table.insert(objects, {self.x + x, self.y + y, curh})
  1887.                 elseif self.o == "x" then
  1888.                     y = self.r * math.cos(angle_increment * j)
  1889.                     z = self.r * math.sin(angle_increment * j)
  1890.                     table.insert(objects, {curh, self.y + y, self.z + z})
  1891.                 elseif self.o == "y" then
  1892.                     x = self.r * math.cos(angle_increment * j)
  1893.                     z = self.r * math.sin(angle_increment * j)
  1894.                     table.insert(objects, {self.x + x, curh, self.z + z})
  1895.                 end
  1896.             end
  1897.            
  1898.             -- Lower the lateral
  1899.             curh = curh - latdist
  1900.         end
  1901.    
  1902.     elseif self.t == "rectprism" then
  1903.    
  1904.         local d = density / 2
  1905.         local x_inc = self.lx / d
  1906.         local y_inc = self.ly / d
  1907.         local z_inc = self.lz / d
  1908.        
  1909.         for z = 0, d do
  1910.             if z == 0 or z == d then
  1911.                 for y = 0, d do
  1912.                     for x = 0, d do
  1913.                         local x_coord = self.xlow + (x * x_inc)
  1914.                         local y_coord = self.ylow + (y * y_inc)
  1915.                         local z_coord = self.zlow + (z * z_inc)
  1916.                         table.insert(objects, {x_coord, y_coord, z_coord})
  1917.                     end
  1918.                 end
  1919.             else           
  1920.                 for y = 0, d do
  1921.                     if y == 0 or y == d then
  1922.                         for x = 0, d do
  1923.                             local x_coord = self.xlow + (x * x_inc)
  1924.                             local y_coord = self.ylow + (y * y_inc)
  1925.                             local z_coord = self.zlow + (z * z_inc)
  1926.                             table.insert(objects, {x_coord, y_coord, z_coord})
  1927.                         end
  1928.                     else
  1929.                         local y_coord = self.ylow + (y * y_inc)
  1930.                         local z_coord = self.zlow + (z * z_inc)
  1931.                         table.insert(objects, {self.xlow, y_coord, z_coord})
  1932.                         table.insert(objects, {self.xhigh, y_coord, z_coord})
  1933.                     end
  1934.                 end
  1935.             end
  1936.         end
  1937.     end
  1938.    
  1939.     local antiobjs = {}
  1940.     -- Also show the surface or perimeter of AntiGEOs with Overshield powerups
  1941.     for name,bool in pairs(GEO[self.n].anti) do
  1942.         if bool then
  1943.             local geo = GEO.get(name)
  1944.             if geo:type() == "sphere" or geo:type() == "cylinder" or geo:type() == "rectprism" then
  1945.                 local a = geo:surface(density / 2, gettagid("eqip", "powerups\\over shield"))
  1946.                 for k,v in ipairs(a) do
  1947.                     table.insert(antiobjs, v)
  1948.                 end
  1949.             else
  1950.                 geo:perimeter(density / 2, gettagid("eqip", "powerups\\over shield"))
  1951.             end
  1952.         end
  1953.     end
  1954.    
  1955.     local objs = {}
  1956.    
  1957.     for k,v in ipairs(objects) do
  1958.         local objId = createobject(mapId, 0, nil, false, v[1], v[2], v[3])
  1959.         GEO[self.n].p[objId] = {v[1], v[2], v[3]}
  1960.         writebit(getobject(objId) + 0x10, 5, true)  -- Ignore pyhsics
  1961.         table.insert(objs, objId)
  1962.     end
  1963.    
  1964.     return objs, antiobjs
  1965. end
  1966.  
  1967. function GEO:hide()
  1968.  
  1969.     if self.p then
  1970.         for k,v in pairs(GEO[self.n].p) do
  1971.             if getobject(k) then
  1972.                 destroyobject(k)
  1973.                 GEO[self.n].p[k] = nil
  1974.             end
  1975.         end
  1976.     end
  1977. end
  1978.  
  1979. function GEO:contains(objId)
  1980.  
  1981.     for k,v in pairs(GEO[self.n].anti) do
  1982.         if GEO[k]:contains(objId) then
  1983.             return false
  1984.         end
  1985.     end
  1986.  
  1987.     if self.t == "sphere" then
  1988.         return inSphere(objId, self.x, self.y, self.z, self.r)
  1989.     elseif self.t == "rectprism" or self.t == "rectangle" then
  1990.         return inRectPrism(objId, self.xlow, self.xhigh, self.ylow, self.yhigh, self.zlow, self.zhigh)
  1991.     elseif self.t == "circle" then
  1992.         return inCircle(objId, self.x, self.y, self.z, self.r, self.o)
  1993.     elseif self.t == "cylinder" then
  1994.         return inCylinder(objId, self.x, self.y, self.z, self.hlow, self.hhigh, self.r, self.o)
  1995.     end
  1996. end
  1997.  
  1998. function GEO:containspoint(point)
  1999.  
  2000.     local bool
  2001.     if GEO[self.n]:vectorintersect(point, point) then
  2002.         bool = true
  2003.     else
  2004.         bool = false
  2005.     end
  2006.    
  2007.     for k,v in pairs(GEO[self.n].anti) do
  2008.         if GEO[k]:containspoint(point) then
  2009.             bool = false
  2010.         end
  2011.     end
  2012.    
  2013.     return bool
  2014. end
  2015.  
  2016. function GEO:vectorintersect(pointA, pointB)
  2017.  
  2018.     local tempA = {x = pointA[1], y = pointA[2], z = pointA[3]}
  2019.     local tempB = {x = pointB[1], y = pointB[2], z = pointB[3]}
  2020.    
  2021.     pointA, pointB = tempA, tempB
  2022.  
  2023.     local points
  2024.     -- Call the appropriate function depending on the type of GEO.
  2025.     if self.t == "sphere" then
  2026.         points = sphereintersect(self, pointA, pointB)
  2027.     elseif self.t == "cylinder" then
  2028.         points = cylinderintersect(self, pointA, pointB)
  2029.     elseif self.t == "rectprism" then
  2030.         points = rectprismintersect(self, pointA, pointB)
  2031.     elseif self.t == "circle" then
  2032.         points = circleintersect(self, pointA, pointB)
  2033.     elseif self.t == "rect" then
  2034.         points = rectangleintersect(self, pointA, pointB)
  2035.     end
  2036.    
  2037.     -- Return points as 2 variables where point = {x, y, z}.
  2038.     local point1, point2
  2039.     if points[1] then
  2040.         point1 = {points[1].x, points[1].y, points[1].z}
  2041.     end
  2042.    
  2043.     if points[2] then
  2044.         point2 = {points[2].x, points[2].y, points[2].z}
  2045.     end
  2046.    
  2047.     -- Check if the points of intersection are within AntiGEOs.
  2048.     for k,v in pairs(GEO[self.n].anti) do
  2049.         if GEO[k]:containspoint(point1) then
  2050.             point1 = nil
  2051.             point2 = point1
  2052.         end
  2053.        
  2054.         if GEO[k]:containspoint(point2) then
  2055.             point2 = nil
  2056.         end
  2057.     end
  2058.    
  2059.     return point1, point2
  2060. end
  2061.  
  2062. function GEO:randcoord()
  2063.  
  2064.     if self.t == "sphere" then
  2065.         return randomInSphere(self.x, self.y, self.z, self.r)
  2066.     elseif self.t == "circle" then
  2067.         return randomInCircle(self.x, self.y, self.z, self.r, self.o)
  2068.     elseif self.t == "cylinder" then
  2069.         return randomInCylinder(self.x, self.y, self.z, self.hlow, self.hhigh, self.r, self.o)
  2070.     elseif self.t == "rectprism" or self.t == "rect" then
  2071.         return randomInRectPrism(self.xlow, self.xhigh, self.ylow, self.yhigh, self.zlow, self.zhigh)
  2072.     end
  2073. end
  2074.  
  2075. function GEO:follow(objId)
  2076.  
  2077.     -- If the objId exists...
  2078.     if getobject(objId) then
  2079.         GEO[self.n].f = objId  -- Save it (the GEOTimer will access it)
  2080.         -- Check to see if the object is a player
  2081.         if objectidtoplayer(objId) then
  2082.             GEO[self.n].player = true
  2083.         end
  2084.     end
  2085. end
  2086.  
  2087. function GEO:unfollow()
  2088.  
  2089.     -- Nullify the saved objId from GEO:follow()
  2090.     GEO[self.n].f = nil
  2091. end
  2092.  
  2093. function GEO.followedBy(objId)
  2094.  
  2095.     -- Initialize table
  2096.     local geos = {}
  2097.        
  2098.     -- Loop through the GEO table
  2099.     for k,v in pairs(GEO) do
  2100.         if type(v) == "table" and k ~= "__index" then  -- make sure we're actually getting a GEO object
  2101.             if v.f == objId then
  2102.                 -- If this GEO has this objId saved, insert it into the geos table.
  2103.                 table.insert(geos, v)
  2104.             end
  2105.         end
  2106.     end
  2107.        
  2108.     -- Return the GEOs following objId in a table.
  2109.     return geos
  2110. end
  2111.  
  2112. function GEO.cleanup(player)
  2113.  
  2114.     local m_player = getplayer(player)
  2115.     local objId = readdword(m_player, 0x34)
  2116.     local geos = GEO.followedBy(objId)
  2117.     for k,v in ipairs(geos) do
  2118.         v:delete()
  2119.     end
  2120. end
  2121.  
  2122. function GEO:velocity(x, y, z)
  2123.  
  2124.     GEO[self.n].vx = x or GEO[self.n].vx
  2125.     GEO[self.n].vy = y or GEO[self.n].vy
  2126.     GEO[self.n].vz = z or GEO[self.n].vz
  2127. end
  2128.  
  2129. function GEO:killzone(bool, delay, kill_message, func, ...)
  2130.  
  2131.     if bool == true then
  2132.         GEO[self.n].kz = true
  2133.     elseif bool == false then
  2134.         GEO[self.n].kz = false
  2135.     elseif bool == nil then
  2136.         return GEO[self.n].kz or false
  2137.     end
  2138.    
  2139.     GEO[self.n].kzdelay = delay
  2140.     GEO[self.n].kzmessage = kill_message
  2141.     GEO[self.n].kztime = {}
  2142.     GEO[self.n].kzfunc = func
  2143.     GEO[self.n].kzargs = {...}
  2144. end
  2145.  
  2146. function GEO:damagezone(bool, damage, delay, damage_message, func, ...)
  2147.  
  2148.     if bool == true then
  2149.         GEO[self.n].damage = damage or 1  -- Amount of damage applied per second.
  2150.     elseif bool == false then
  2151.         GEO[self.n].damage = nil
  2152.     elseif bool == nil then  -- If nothing is passed, return true if this GEO is a damagezone, false if not.
  2153.         if GEO[self.n].damage then
  2154.             return true
  2155.         else
  2156.             return false
  2157.         end
  2158.     end
  2159.    
  2160.     GEO[self.n].dmgdelay = delay
  2161.     GEO[self.n].dmgmessage = damage_message
  2162.     GEO[self.n].dmgmsgsent = {}
  2163.     GEO[self.n].dmgtime = {}
  2164.     GEO[self.n].dmgfunc = func
  2165.     GEO[self.n].dmgargs = {...}
  2166. end
  2167.  
  2168. function GEO:face(orientation, direction)
  2169.  
  2170.     -- If this is a rectangular prism...
  2171.     if self.t == "rectprism" then
  2172.         orientation = orientation or "z"
  2173.         direction = direction or "+"
  2174.         if orientation == "z" then
  2175.             local width = self.lx
  2176.             local height = self.ly
  2177.             local highlow
  2178.             if direction == "+" then
  2179.                 highlow = self.zhigh
  2180.             else
  2181.                 highlow = self.zlow
  2182.             end
  2183.                
  2184.             -- Create a new rectangle which overlays the specified face and return that rectangle.
  2185.             return GEO.newRect(self.n .. "ZFace" .. os.time(), width, height, self.x, self.y, highlow, orientation)
  2186.                
  2187.         elseif orientation == "x" then
  2188.             local width = self.ly
  2189.             local height = self.lz
  2190.             local highlow
  2191.             if direction == "+" then
  2192.                 highlow = self.xhigh
  2193.             else
  2194.                 highlow = self.xlow
  2195.             end
  2196.                
  2197.             return GEO.newRect(self.n .. "XFace" .. os.time(), width, height, highlow, self.y, self.z, orientation)
  2198.            
  2199.         elseif orientation == "y" then
  2200.             local width = self.lx
  2201.             local height = self.lz
  2202.             local highlow
  2203.             if direction == "+" then
  2204.                 highlow = self.yhigh
  2205.             else
  2206.                 highlow = self.ylow
  2207.             end
  2208.                
  2209.             return GEO.newRect(self.n .. "YFace" .. os.time(), width, height, self.x, highlow, self.z, orientation)
  2210.         end
  2211.        
  2212.     -- If this is a cylinder...
  2213.     elseif self.t == "cylinder" then
  2214.         if orientation == self.o then
  2215.             local highlow
  2216.             if direction == "+" then
  2217.                 highlow = self.hhigh
  2218.             else
  2219.                 highlow = self.hlow
  2220.             end
  2221.                
  2222.             -- Return a new circle which overlays the specified face and return that circle.
  2223.             if orientation == "z" then
  2224.                 return GEO.newCircle(self.n .. "ZFace" .. os.time(), self.r, self.x, self.y, highlow, self.o)
  2225.             elseif orientation == "x" then
  2226.                 return GEO.newCircle(self.n .. "XFace" .. os.time(), self.r, highlow, self.y, self.z, self.o)
  2227.             elseif orientation == "y" then
  2228.                 return GEO.newCircle(self.n .. "YFace" .. os.time(), self.r, self.x, highlow, self.z, self.o)
  2229.             end
  2230.         else
  2231.             hprintf("You may only retrieve the orientation face of a cylinder.")
  2232.         end
  2233.     end
  2234. end
  2235.  
  2236. function GEO:copy(name)
  2237.  
  2238.     name = name or self.n .. "Copy" .. os.time()
  2239.     if not GEO[name] then
  2240.         GEO[name] = self
  2241.         return GEO[name]
  2242.     end
  2243. end
  2244.  
  2245. function GEO:monitor(objId)
  2246.  
  2247.     if getobject(objId) then
  2248.         GEO[self.n].m[objId] = true
  2249.         monitored[objId] = true
  2250.     end
  2251. end
  2252.  
  2253. registertimer(10, "GEOTimer")
  2254.  
  2255. pcoords = pcoords or {}  -- Keeps track of previous coordinates of objects being monitored
  2256. monitored = monitored or {}  -- Keeps track of non-player objects being monitored
  2257.  
  2258. function GEOTimer(id, count)
  2259.        
  2260.     -- Loop through the GEO table
  2261.     for k,v in pairs(GEO) do
  2262.         if type(v) == "table" and k ~= "__index" then
  2263.             -- If this GEO is following an object...
  2264.             if v.f then
  2265.                 -- Get the coordinates of the object
  2266.                 local x, y, z = getobjectcoords(v.f)
  2267.                 if x then  -- If this object exists...
  2268.                     if v.player then
  2269.                         local player = objectidtoplayer(v.f)
  2270.                         if player then
  2271.                             local m_player = getplayer(player)  -- See if the player is still here
  2272.                             if m_player then  -- If they are...
  2273.                                 local time_until_respawn = readdword(m_player, 0x2C)  -- Check to see if they're dead
  2274.                                 if time_until_respawn == 0 then  -- If they're not...
  2275.                                     if v.p then  -- If this GEO has perimeter objects...
  2276.                                         for objId, coords in pairs(v.p) do
  2277.                                             if getobject(objId) then
  2278.                                                 local ox, oy, oz = table.unpack(coords)
  2279.                                                 local x_diff = x - v.x
  2280.                                                 local y_diff = y - v.y
  2281.                                                 local z_diff = z - v.z
  2282.                                                 local m_object = getobject(objId)
  2283.                                                 movobjectcoords(objId, ox + x_diff, oy + y_diff, oz + z_diff)  -- Move them with the GEO.
  2284.                                                 GEO[v.n].p[objId] = {ox + x_diff, oy + y_diff, oz + z_diff}
  2285.                                             end
  2286.                                         end
  2287.                                     end
  2288.                                     v:move(x, y, z)  -- Move the GEO to the player's coordinates
  2289.                                 else  -- Otherwise...
  2290.                                     v:delete()  -- Delete the GEO.
  2291.                                 end
  2292.                             else  -- Otherwise...
  2293.                                 v:delete()  -- Delete the GEO.
  2294.                             end
  2295.                         else  -- Otherwise...
  2296.                             v:delete()  -- Delete the GEO.
  2297.                         end
  2298.                     else  -- Otherwise...
  2299.                         if v.p then  -- If this GEO has perimeter objects...
  2300.                             for objId, coords in pairs(v.p) do
  2301.                                 if getobject(objId) then
  2302.                                     local ox, oy, oz = table.unpack(coords)
  2303.                                     local x_diff = x - v.x
  2304.                                     local y_diff = y - v.y
  2305.                                     local z_diff = z - v.z
  2306.                                     local m_object = getobject(objId)
  2307.                                     movobjectcoords(objId, ox + x_diff, oy + y_diff, oz + z_diff)  -- Move them with the GEO.
  2308.                                     GEO[v.n].p[objId] = {ox + x_diff, oy + y_diff, oz + z_diff}
  2309.                                 end
  2310.                             end
  2311.                         end
  2312.                            
  2313.                         v:move(x, y, z)  -- Don't worry about all of that player nonsense and just move the damn GEO.
  2314.                     end
  2315.                 else  -- Otherwise...
  2316.                     v:delete()  -- Delete the GEO.
  2317.                 end
  2318.             end
  2319.                
  2320.             -- If after all of that following nonsense, we still have a GEO...
  2321.             if v then
  2322.                 -- If this GEO is a killzone...
  2323.                 if v.kz then
  2324.                     -- Check if anyone is inside of it.
  2325.                     for i = 0,15 do
  2326.                         local m_player = getplayer(i)
  2327.                         if m_player then
  2328.                             local objId = readdword(m_player, 0x34)
  2329.                             if getobject(objId) then
  2330.                                 if v:contains(objId) then
  2331.                                     local allow = true
  2332.                                     -- If this killzone has a function...
  2333.                                     if v.kzfunc then
  2334.                                         -- If this function returns false...
  2335.                                         if not v.kzfunc(v, i, table.unpack(v.kzargs)) then
  2336.                                             allow = false
  2337.                                         end
  2338.                                     end
  2339.                                     -- If the function allows this player to be killed by this GEO...
  2340.                                     if allow then
  2341.                                         -- If this killzone kills on a delay...
  2342.                                         if v.kzdelay then
  2343.                                             GEO[k].kztime[i] = (v.kztime[i] or 0) + (os.time() - (lasttime or os.time()))
  2344.                                             if GEO[k].kztime[i] >= GEO[k].kzdelay then
  2345.                                                 kill(i)  -- Time's up.
  2346.                                                 if GEO[k].kzmessage then
  2347.                                                     privatesay(i, v.kzmessage)
  2348.                                                 end
  2349.                                                
  2350.                                                 GEO[k].kztime[i] = 0
  2351.                                             end
  2352.                                         else
  2353.                                             kill(i)  -- Kill that ho.
  2354.                                             GEO[k].kztime[i] = 0
  2355.                                         end
  2356.                                     end
  2357.                                 end
  2358.                             else
  2359.                                 GEO[k].kztime[i] = 0
  2360.                             end
  2361.                         end
  2362.                     end
  2363.                 end
  2364.                
  2365.                 -- If this GEO is a damagezone...
  2366.                 if v.damage then
  2367.                     -- Check if anyone is inside of it.
  2368.                     for i = 0,15 do
  2369.                         local m_player = getplayer(i)
  2370.                         if m_player then
  2371.                             local objId = readdword(m_player, 0x34)
  2372.                             if getobject(objId) then
  2373.                                 if v:contains(objId) then
  2374.                                     local allow = true
  2375.                                     -- If this damagezone has a function...
  2376.                                     if v.dmgfunc then
  2377.                                         -- If this function returns false...
  2378.                                         if not v.dmgfunc(v,  i, table.unpack(v.dmgargs)) then
  2379.                                             allow = false
  2380.                                         end
  2381.                                     end
  2382.                                     -- If the function allows this player to be damaged by this GEO...
  2383.                                     if allow then
  2384.                                         -- If this damagezone is on a delay...
  2385.                                         if v.dmgdelay then                                         
  2386.                                             GEO[k].dmgtime[i] = (v.dmgtime[i] or 0) + (os.time() - (lasttime or os.time()))
  2387.                                             if GEO[k].dmgtime[i] >= GEO[k].dmgdelay then
  2388.                                                 applydmg(objId, v.damage / 100)  -- Apply damage
  2389.                                                 -- Check to see if the player has already received the message
  2390.                                                 if not GEO[k].dmgmsgsent[i] then
  2391.                                                     privatesay(i, v.dmgmessage)
  2392.                                                     GEO[k].dmgmsgsent[i] = true
  2393.                                                 end
  2394.                                             end
  2395.                                         else
  2396.                                             applydmg(objId, v.damage / 100)  -- Apply damage
  2397.                                         end
  2398.                                     end
  2399.                                 else
  2400.                                     GEO[k].dmgtime[i] = 0
  2401.                                     GEO[k].dmgmsgsent[i] = nil
  2402.                                 end
  2403.                             end
  2404.                         end
  2405.                     end
  2406.                 end
  2407.                    
  2408.                 -- If this GEO is monitoring for objects entering and exiting it...
  2409.                 if v.m then
  2410.                     -- Loop through the table of objects this GEO is monitoring for.
  2411.                     for objId,_ in pairs(v.m) do
  2412.                         -- If this object still exists...
  2413.                         if getobject(objId) then
  2414.                             -- If this object is inside of the GEO...
  2415.                             if v:contains(objId) then
  2416.                                 local player = objectidtoplayer(objId)
  2417.                                 -- If the GEO didn't know this object was inside of it 1/100 of a second ago...
  2418.                                 if not v.c[objId] then
  2419.                                     -- Call OnGeoEnter.
  2420.                                     local allow = OnGeoEnter(v, player, objId)
  2421.                                     if allow == 0 or allow == false then
  2422.                                         if pcoords[objId] then
  2423.                                             movobjectcoords(objId, table.unpack(pcoords[objId]))
  2424.                                         end
  2425.                                     else
  2426.                                         GEO[k].c[objId] = true
  2427.                                     end
  2428.                                 end
  2429.                             else  -- Otherwise...
  2430.                                 -- If the GEO thought this object was inside of it 1/100 of a second ago...
  2431.                                 if v.c[objId] then
  2432.                                     local player = objectidtoplayer(objId)
  2433.                                     -- Call OnGeoExit.
  2434.                                     local allow = OnGeoExit(v, player, objId)
  2435.                                     if allow == 0 or allow == false then
  2436.                                         if pcoords[objId] then
  2437.                                             movobjectcoords(objId, table.unpack(pcoords[objId]))
  2438.                                         end
  2439.                                     else
  2440.                                         GEO[k].c[objId] = nil
  2441.                                     end
  2442.                                 end
  2443.                             end
  2444.                         else  -- Otherwise...
  2445.                             GEO[k].m[objId] = nil  -- Stop monitoring for this object.
  2446.                         end
  2447.                     end
  2448.                 end
  2449.                    
  2450.                 -- Automatically monitor for players
  2451.                 -- Loop through every player to check if they are in this GEO.
  2452.                 for i = 0, 15 do
  2453.                     local m_player = getplayer(i)
  2454.                     -- If this player exists...
  2455.                     if m_player then
  2456.                         local objId = readdword(m_player, 0x34)
  2457.                         -- If this player is alive...
  2458.                         if getobject(objId) then
  2459.                             -- If this object is inside of the GEO...
  2460.                             if v:contains(objId) then
  2461.                                 local player = objectidtoplayer(objId)
  2462.                                 -- If the GEO didn't know this object was inside of it 1/100 of a second ago...
  2463.                                 if not v.c[objId] then
  2464.                                     -- Call OnGeoEnter.
  2465.                                     local allow = OnGeoEnter(v, player, objId)
  2466.                                     if allow == 0 or allow == false then
  2467.                                         if pcoords[objId] then
  2468.                                             movobjectcoords(objId, table.unpack(pcoords[objId]))
  2469.                                         end
  2470.                                     else
  2471.                                         GEO[k].c[objId] = true
  2472.                                     end
  2473.                                 end
  2474.                             else  -- Otherwise...
  2475.                                 -- If the GEO thought this object was inside of it 1/100 of a second ago...
  2476.                                 if v.c[objId] then
  2477.                                     -- Call OnGeoExit.
  2478.                                     local allow = OnGeoExit(v, i, objId)
  2479.                                     if allow == 0 or allow == false then
  2480.                                         if pcoords[objId] then
  2481.                                             movobjectcoords(objId, table.unpack(pcoords[objId]))
  2482.                                         end
  2483.                                     else
  2484.                                         GEO[k].c[objId] = nil
  2485.                                     end
  2486.                                 end
  2487.                             end
  2488.                         end
  2489.                     end
  2490.                 end
  2491.                    
  2492.                 -- If this GEO has a velocity...
  2493.                 if v.vx or v.vy or v.vz then
  2494.                     if v.p then  -- If this GEO has perimeter objects...
  2495.                         for objId, coords in pairs(v.p) do
  2496.                             if getobject(objId) then
  2497.                                 local ox, oy, oz = table.unpack(coords)
  2498.                                 local m_object = getobject(objId)
  2499.                                 movobjectcoords(objId, ox + (v.vx or 0), oy + (v.vy or 0), oz + (v.vz or 0))  -- Move them with the GEO.
  2500.                                 GEO[v.n].p[objId] = {ox + (v.vx or 0), oy + (v.vy or 0), oz + (v.vz or 0)}
  2501.                             end
  2502.                         end
  2503.                     end
  2504.                     -- Move that ho.
  2505.                     v:move(v.x + (v.vx or 0) / 100, v.y + (v.vy or 0) / 100, v.z + (v.vz or 0) / 100)
  2506.                 end
  2507.             end
  2508.         end
  2509.     end
  2510.        
  2511.     -- Update coordinates at a slight delay for blocking GEO entry/exit (if there is no delay, players in mid-air could end up dying since they'll just be teleported in mid-air perpetually)
  2512.     if count % 25 == 0 then
  2513.         -- Each player
  2514.         for i = 0,15 do
  2515.             local m_player = getplayer(i)
  2516.             if m_player then
  2517.                 local objId = readdword(m_player, 0x34)
  2518.                 if getobject(objId) then
  2519.                     pcoords[objId] = {getobjectcoords(objId)}
  2520.                 else
  2521.                     pcoords[objId] = nil
  2522.                 end
  2523.             end
  2524.         end
  2525.            
  2526.         -- Each object being monitored
  2527.         for objId,_ in pairs(monitored) do
  2528.             if getobject(objId) then
  2529.                 pcoords[objId] = {getobjectcoords(objId)}
  2530.             else
  2531.                 pcoords[objId] = nil
  2532.                 monitored[objId] = nil
  2533.             end
  2534.         end
  2535.     end
  2536.        
  2537.     lasttime = os.time()  -- Keeps track of the os time the GEOTimer last executed
  2538.        
  2539.     return true
  2540. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement