summaryrefslogtreecommitdiff
path: root/lua/zones.lua
diff options
context:
space:
mode:
authorAlexander Pickering <alexandermpickering@gmail.com>2017-08-08 17:42:45 -0400
committerAlexander Pickering <alexandermpickering@gmail.com>2017-08-08 17:42:45 -0400
commitebb3ac7ef4ac1e69fcfc576dc7a06b269d42ca53 (patch)
treea8344265ccaa8422f517e633faa4f34cf9e52db9 /lua/zones.lua
parentdaa59a7835c350a09dcb207c714acf57828137f3 (diff)
downloadartery_editor-ebb3ac7ef4ac1e69fcfc576dc7a06b269d42ca53.tar.gz
artery_editor-ebb3ac7ef4ac1e69fcfc576dc7a06b269d42ca53.tar.bz2
artery_editor-ebb3ac7ef4ac1e69fcfc576dc7a06b269d42ca53.zip
Integrated zones into artery editor
Diffstat (limited to 'lua/zones.lua')
-rw-r--r--lua/zones.lua451
1 files changed, 451 insertions, 0 deletions
diff --git a/lua/zones.lua b/lua/zones.lua
new file mode 100644
index 0000000..c005c07
--- /dev/null
+++ b/lua/zones.lua
@@ -0,0 +1,451 @@
+
+local version = 1.0 -- Older versions will not run if a newer version is used in another script.
+--[[
+ ZONES - by Bobbleheadbob
+ WARNING: If you edit any of these files, make them use a different namespace. Multiple scripts may depend on this library so modifying it can break other scripts.
+
+ Purpose:
+ For easy in-game designation of persistent polygonal zones which are used by any script.
+
+ How to Use:
+ All zones are saved in zones.List; see an example below.
+ Zone creation is handled with weapon_zone_designator and ent_zone_point, but can be done in code as well.
+ When a zone is created, changed, or removed all zones are synced to clients. When clients join they are also synced.
+ Any extra details can be saved to a zone. Everything is written to a txt file and is persistent to the map.
+
+ Since multiple scripts might use the zones system, don't assume that every zone is related to your script.
+ To register a zone class, use zones.RegisterClass(class, color); use a unique string like "Scriptname Room".
+ When a zone class is registered, admins can use the tool to create new ones.
+ When a new zone is created, the "ShowZoneOptions" hook is called clientside. See the hook below for documentation.
+
+ Use zones.FindByClass() to find all zones which are of a given class.
+ Use ply:GetCurrentZone() to find the zone that a player is standing in.
+
+ Installation:
+ This is a shared file so include it in any shared environment. Also include ent_zone_point and weapon_zone_designator as a shared ent and weapon.
+ You should not put this file directly in lua/autorun.
+
+ License:
+ YOU MAY use/edit this however you want.
+ YOU MAY distribute this with other scripts whether they are paid or free.
+ YOU MAY NOT distribute this on its own. It must accompany another script.
+
+ Enjoy! ~Bobbleheadbob
+]]
+
+local table, math, Vector, pairs, ipairs, ents = table, math, Vector, pairs, ipairs, ents
+
+if zones then
+ if zones.version > version then
+ print("A new version of zones exists. Using version "..zones.version.." instead of "..version)
+ return
+ elseif zones.version < version then
+ print("A new version of zones exists. Using version "..version.." instead of "..zones.version)
+ end
+else
+ print("Loaded zones " ..version)
+end
+
+zones = zones or {}
+zones.version = version
+
+zones.List = zones.List or {}
+--[[ --Example of a zone.
+ zones.List[1] = { -- 1 is the zone ID. Automatically assigned.
+
+ -- points, height, and class are reserved.
+ points = { --List of areas in 3D space which define the zone.
+ { --each area is a list of points. Areas should intersect with one another but they don't have to.
+ Vector(),
+ Vector(),
+ Vector(),
+ },
+ {
+ Vector(),
+ Vector(),
+ Vector(),
+ },
+ },
+ height = {200,100}, -- How tall each area of the zone is. Each entry corresponds to an area listed above.
+ class = "GMaps Area", -- Zones with different classes are created and treated separately. Use zones.RegisterClass to create a new one.
+
+ -- Zones can have any other values saved to them. If you save a player, make sure to save it as a steamid.
+
+ }
+]]
+
+zones.Classes = zones.Classes or {}
+
+//Common interface functions
+
+-- Registers a zone class which can then be created using weapon_zone_designator
+function zones.RegisterClass(class,color)
+ zones.Classes[class] = color
+end
+
+
+local plymeta = FindMetaTable("Player")
+--returns one of the zones a player is found in. Also returns that zone's ID. Class is optional to filter the search.
+function plymeta:GetCurrentZone(class)
+ return zones.GetZoneAt(self:GetPos(),class)
+end
+
+--returns a table of zones the player is in. Class is optional to filter the search.
+function plymeta:GetCurrentZones(class)
+ return zones.GetZonesAt(self:GetPos(),class)
+end
+
+function zones.GetZoneAt(pos,class) --works like above, except uses any point.
+ for k,zone in pairs(zones.List) do
+ if class and class != zone.class then continue end
+ for k1, points in pairs(zone.points) do
+ if zones.PointInPoly(pos,points) then
+ local z = points[1].z
+ if pos.z >= z and pos.z < z + zone.height[k1] then
+ return zone,k
+ end
+ end
+ end
+ end
+ return nil, -1
+end
+function zones.GetZonesAt(pos,class) --works like above, except uses any point.
+ local tbl = {}
+ for k,zone in pairs(zones.List) do
+ if class and class != zone.class then continue end
+ for k1, points in pairs(zone.points) do
+ if zones.PointInPoly(pos,points) then
+ local z = points[1].z
+ if pos.z >= z and pos.z < z + zone.height[k1] then
+ tbl[k] = zone
+ end
+ end
+ end
+ end
+ return tbl
+end
+
+--Gets a list of all zones which are of the specified class.
+function zones.FindByClass(class)
+ local tbl = {}
+
+ for k,v in pairs(zones.List) do
+ if v.class == class then
+ tbl[k] = v
+ end
+ end
+
+ return tbl
+end
+
+
+if SERVER then
+ util.AddNetworkString("zones_sync")
+ util.AddNetworkString("zones_class")
+
+ function zones.SaveZones()
+ if not file.Exists("zones","DATA") then
+ file.CreateDir("zones")
+ end
+ file.Write("zones/"..game.GetMap():gsub("_","-"):lower()..".txt", util.TableToJSON(zones.List))
+ end
+ concommand.Add("zone_save",function(ply,c,a)
+ if not ply:IsAdmin() then return end
+ zones.SaveZones()
+ end)
+
+ function zones.LoadZones()
+ local tbl = file.Read("zones/"..game.GetMap():gsub("_","-"):lower()..".txt", "DATA")
+ zones.List = tbl and util.JSONToTable(tbl) or {}
+ end
+
+ local sync = false
+ function zones.Sync(ply)
+ sync = true
+ end
+ hook.Add("Tick","zones_sync",function()
+ if sync then
+ net.Start("zones_sync")
+ net.WriteTable(zones.List)
+ if ply then
+ net.Send(ply)
+ else
+ net.Broadcast()
+ end
+ sync = false
+ end
+ end)
+
+ function zones.CreateZoneFromPoint(ent)
+
+ local zone = {
+ points = {{}}, --only 1 area when creating a new zone.
+ height = {ent:GetTall()},
+ class = ent:GetZoneClass()
+ }
+
+ local id = table.maxn(zones.List) + 1
+ local cur = ent
+ repeat
+
+ zone.points[1][#zone.points[1]+1] = cur:GetPos() - Vector(0,0,2)
+
+ cur:SetZoneID(id)
+ cur = cur:GetNext()
+
+ until (cur == ent)
+
+ zones.List[id] = zone
+ zones.Sync()
+
+ hook.Run("OnZoneCreated",zone,zone.class,id)
+
+ return zone, id
+
+ end
+
+ function zones.Remove(id)
+ zones.List[id] = nil
+ zones.Sync()
+ end
+
+ function zones.CreatePointEnts(removeThese) --removeThese is optional.
+ for k,v in pairs(removeThese or ents.FindByClass("ent_zone_point")) do --remove old
+ v:Remove()
+ end
+
+ --create new
+ for id,zone in pairs(zones.List)do
+
+ for k, area in pairs(zone.points) do
+
+ local first
+ local curr
+ for k2,point in ipairs(area)do
+
+ local next = ents.Create("ent_zone_point")
+
+ if IsValid(curr) then
+ next:SetPos(point+Vector(0,0,1))
+ curr:SetNext(next)
+ -- curr:DeleteOnRemove(next)
+ else
+ first = next
+ next:SetPos(point+Vector(0,0,1))
+ end
+
+ next.LastPoint = curr
+ curr = next
+ next:SetTall(zone.height[k])
+ next:SetZoneClass(zone.class)
+ next:Spawn()
+ next:SetZoneID(id)
+ next:SetAreaNumber(k)
+
+ end
+
+ curr:SetNext(first)
+ -- curr:DeleteOnRemove(first)
+ first.LastPoint = curr
+
+ end
+ end
+
+ end
+
+ function zones.Merge(from,to)
+
+ local zfrom, zto = zones.List[from], zones.List[to]
+
+ table.Add(zto.points, zfrom.points)
+ table.Add(zto.height, zfrom.height)
+
+ zones.Remove(from)
+
+ zones.Sync()
+
+ end
+
+ function zones.Split(id,areanum)
+ local zone = zones.List[id]
+ local pts, h = zone.points[areanum], zone.height[areanum]
+
+ table.remove(zone.points,areanum)
+ table.remove(zone.height,areanum)
+
+ if #zone.points == 0 then
+ zones.Remove(id)
+ end
+
+ local new = table.Copy(zone)
+ new.points = {pts}
+ new.height = {h}
+
+ local id = table.maxn(zones.List)+1
+ zones.List[id] = new
+
+ zones.Sync()
+
+ return new,id
+
+ end
+
+ hook.Add("Initialize","claim_load",function()
+ zones.LoadZones()
+ end)
+ hook.Add("PlayerInitialSpawn","claim_sync",function(ply)
+ zones.Sync(ply)
+ end)
+
+ net.Receive("zones_class",function(len,ply)
+ if not ply:IsAdmin() then return end
+ local id = net.ReadFloat()
+ local class = net.ReadString()
+
+ for k,v in pairs(ents.FindByClass("ent_zone_point"))do
+ if v:GetZoneID() == id then
+ v:SetZoneClass(class)
+ end
+ end
+ zones.List[id].class = class
+
+ zones.Sync()
+ end)
+
+else
+ net.Receive("zones_sync",function(len)
+ zones.List = net.ReadTable()
+ end)
+ function zones.ShowOptions(id)
+
+ local zone = zones.List[id]
+ local class = zone.class
+
+ local frame = vgui.Create("DFrame")
+ frame:MakePopup()
+ frame:SetTitle("Zone Settings")
+
+ local ztitle = vgui.Create("DLabel",frame)
+ ztitle:Dock(TOP)
+ ztitle:DockMargin(2,0,5,5)
+ ztitle:SetText("Zone Class:")
+ ztitle:SizeToContents()
+
+ local zclass = vgui.Create("DComboBox",frame)
+ zclass:Dock(TOP)
+ zclass:DockMargin(0,0,0,5)
+ for k,v in pairs(zones.Classes) do
+ zclass:AddChoice(k,nil,k == class)
+ end
+ function zclass:OnSelect(i,class)
+ net.Start("zones_class")
+ net.WriteFloat(id)
+ net.WriteString(class)
+ net.SendToServer()
+
+ frame.content:Remove()
+
+ frame.content = vgui.Create("DPanel",frame)
+ frame.content:Dock(FILL)
+
+ local w,h = hook.Run("ShowZoneOptions",zone,class,frame.content,id,frame)
+ frame:SizeTo((w or 100)+8,(h or 2)+78, .2)
+ frame:MoveTo(ScrW()/2-((w or 292)+8)/2,ScrH()/2-((h or 422)+78)/2, .2)
+ end
+
+ frame.content = vgui.Create("DPanel",frame)
+ frame.content:Dock(FILL)
+
+ local w,h = hook.Run("ShowZoneOptions",zone,class,frame.content,id,frame)
+ frame:SetSize((w or 100)+8,(h or 2)+78)
+ frame:Center()
+
+ end
+
+end
+
+
+
+//returns the point of intersection between two infinite lines.
+local function IntersectPoint(line1, line2)
+
+ local x1,y1,x2,y2,x3,y3,x4,y4 = line1.x1,line1.y1,line1.x2,line1.y2,line2.x1,line2.y1,line2.x2,line2.y2
+
+ local m1,m2 = (y1-y2)/((x1-x2)+.001),(y3-y4)/((x3-x4)+.001) --get the slopes
+ local yint1,yint2 = (-m1*x1)+y1,(-m2*x3)+y3 --get the y-intercepts
+ local x = (yint1-yint2)/(m2-m1) --calculate x pos
+ local y = m1*x+yint1 --plug in x pos to get y pos
+
+ return x,y
+
+end
+//Returns a bool if two SEGEMENTS intersect or not.
+local function Intersect(line1, line2)
+
+ local x,y = IntersectPoint(line1, line2)
+ local sx,sy = tostring(x), tostring(y)
+ if (sx == "-inf" or sx == "inf" or sx == "nan") then
+ return false
+ end
+
+ local minx1, maxx1 = math.min(line1.x1,line1.x2)-.1, math.max(line1.x1,line1.x2)+.1
+ local minx2, maxx2 = math.min(line2.x1,line2.x2)-.1, math.max(line2.x1,line2.x2)+.1
+ local miny1, maxy1 = math.min(line1.y1,line1.y2)-.1, math.max(line1.y1,line1.y2)+.1
+ local miny2, maxy2 = math.min(line2.y1,line2.y2)-.1, math.max(line2.y1,line2.y2)+.1
+
+ if (x >= minx1) and (x <= maxx1) and (x >= minx2) and (x <= maxx2) then
+
+ if (y >= miny1) and (y <= maxy1) and (y >= miny2) and (y <= maxy2) then
+
+ --debugoverlay.Sphere( Vector(x,y,LocalPlayer():GetPos().z), 3, FrameTime()+.01, Color(255,0,0), true)
+
+ return true
+
+ end
+
+ end
+
+ return false
+
+end
+function zones.PointInPoly(point,poly) //True if point is within a polygon.
+
+ //Check validity
+ local lines = {}
+ local pcount = #poly
+ for k1=1, pcount do
+
+ local k2 = k1+1
+ if k2 > pcount then
+ k2 = 1
+ end
+
+ lines[k1] = {
+ x1 = poly[k1].x,
+ y1 = poly[k1].y,
+ x2 = poly[k2].x,
+ y2 = poly[k2].y,
+ valid = true
+ }
+
+ end
+
+ local ray = {
+ x1 = point.x,
+ y1 = point.y,
+ x2 = point.x + 10000,
+ y2 = point.y + 10000
+ }
+ local inside = false
+
+ //Do ray check.
+ for k,v in pairs(lines)do
+
+ if Intersect(ray,v) then
+ inside = !inside
+ end
+
+ end
+
+ return inside
+end