Advertisement
rockbandcheeseman

Death Island Tank Block GEO Example

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