aboutsummaryrefslogtreecommitdiff
path: root/gamemode
diff options
context:
space:
mode:
authorAlexander Pickering <alexandermpickering@gmail.com>2017-11-04 17:53:11 -0400
committerAlexander Pickering <alexandermpickering@gmail.com>2017-11-04 17:53:11 -0400
commit40080dcfde028c64c4f6f51792b928ee91677bc6 (patch)
tree000e1144ee6e58f5574489fbc2793d43e8d68a4a /gamemode
parent84a72c78cd157b2649c03f8f503310883241dcdb (diff)
downloadartery-40080dcfde028c64c4f6f51792b928ee91677bc6.tar.gz
artery-40080dcfde028c64c4f6f51792b928ee91677bc6.tar.bz2
artery-40080dcfde028c64c4f6f51792b928ee91677bc6.zip
Added bobleheadbob's zones
Added zones api and required it from maptich
Diffstat (limited to 'gamemode')
-rw-r--r--gamemode/core/mapstich/cl_mapstich.lua2
-rw-r--r--gamemode/core/mapstich/sv_mapstich.lua1
-rw-r--r--gamemode/core/zones/sh_zones.lua611
3 files changed, 613 insertions, 1 deletions
diff --git a/gamemode/core/mapstich/cl_mapstich.lua b/gamemode/core/mapstich/cl_mapstich.lua
index e1e5fcc..62cad3f 100644
--- a/gamemode/core/mapstich/cl_mapstich.lua
+++ b/gamemode/core/mapstich/cl_mapstich.lua
@@ -1,7 +1,7 @@
--[[
The client constantly cheks to see if we're in a serverchnage zone
]]
-
+nrequire("sh_zones.lua")
hook.Add("Think","artery_checklevelchange",function()
local z = LocalPlayer():GetCurrentZone()
--print("looks like i'm in zone",z)
diff --git a/gamemode/core/mapstich/sv_mapstich.lua b/gamemode/core/mapstich/sv_mapstich.lua
index ff84fca..0a20828 100644
--- a/gamemode/core/mapstich/sv_mapstich.lua
+++ b/gamemode/core/mapstich/sv_mapstich.lua
@@ -1,5 +1,6 @@
--Make sure zones are loaded already
nrequire("sv_mysqlite.lua")
+nrequire("sh_zones.lua")
local q = nrequire("core/database/sv_queries.lua")
--if not zones then error("This thing needs zones to function!") end
diff --git a/gamemode/core/zones/sh_zones.lua b/gamemode/core/zones/sh_zones.lua
new file mode 100644
index 0000000..ca66abb
--- /dev/null
+++ b/gamemode/core/zones/sh_zones.lua
@@ -0,0 +1,611 @@
+local version = 1.20 -- Older versions will not run if a newer version is used in another script.
+--[[
+ ZONES - by Bobbleheadbob with help from Zeh Matt
+ 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 "OnZoneCreated" hook is called serverside. See the example file for documentation.
+ When a zone is loaded into the game, the "OnZoneLoaded" hook is called serverside. See the example file for documentation.
+ When a player edits a zone's properties, the "ShowZoneOptions" hook is called clientside. See the example file 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, as long as you give proper attribution.
+ 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, bit = table, math, Vector, pairs, ipairs, ents, bit
+
+if zones then
+ local diff = math.abs(math.floor(version)-math.floor(zones.version)) > 0
+ if diff then
+ ErrorNoHalt("WARNING! Two scripts use VERY different versions of the zones API. Please tell one of them to update their script!\n")
+ end
+ if zones.version > version then
+ if diff then
+ print("The outdated version of zones is located at: "..debug.getinfo(1,"S").short_src)
+ end
+ print("A new version of zones exists. Using version "..zones.version.." instead of "..version)
+ return
+ elseif zones.version < version then
+ if diff then
+ print("The outdated version of zones is located at: "..debug.getinfo(zones.RegisterClass,"S").short_src)
+ end
+ 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.Classes = zones.Classes or {}
+zones.List = zones.List or {}
+zones.Map = zones.Map 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)
+ local c = zones.Cache[self][class or "___"]
+ if c then return unpack(c) end
+ local z,id = zones.GetZoneAt(self:GetPos(), class)
+ zones.Cache[self][class or "___"] = {z,id}
+ return z,id
+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.
+
+ local nearby = zones.GetNearbyZones(pos)
+
+ for k,zone in pairs(nearby) do
+
+ if class and class != zone.class then continue end
+ if not pos:WithinAABox(zone.bounds.mins, zone.bounds.maxs) 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 = {}
+ local nearby = zones.GetNearbyZones(pos)
+ for k,zone in pairs(nearby) do
+ if class and class != zone.class then continue end
+ if not pos:WithinAABox(zone.bounds.mins,zone.bounds.maxs) 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
+
+--Returns the numerical ID of a zone.
+function zones.GetID(zone)
+ return table.KeyFromValue(zones.List,zone)
+end
+
+
+
+
+//Getting into the meat of the API:
+local mapMins = -16000
+local mapMaxs = 16000
+local mapSize = 32000
+local chunkSize = 128
+
+local function GetZoneIndex(pos)
+
+ local x = pos.x + mapMaxs
+ local y = pos.y + mapMaxs
+ local z = pos.z + mapMaxs
+
+ local idxX = math.floor(x / chunkSize)
+ local idxY = math.floor(y / chunkSize)
+ local idxZ = math.floor(z / chunkSize)
+ local idx = bit.bor(bit.lshift(idxX, 24), bit.lshift(idxY, 14), idxZ)
+
+ return idx
+
+end
+
+local function Floor(x,to)
+ return math.floor(x / to) * to
+end
+local function Ceil(x,to)
+ return math.ceil(x / to) * to
+end
+
+function zones.CreateZoneMapping()
+ zones.Map = {}
+ for _, zone in pairs(zones.List) do
+ local mins = zone.bounds.mins
+ local maxs = zone.bounds.maxs
+ for x = Floor(mins.x,chunkSize), Ceil(maxs.x + 1,chunkSize), chunkSize do
+ for y = Floor(mins.y,chunkSize), Ceil(maxs.y + 1,chunkSize), chunkSize do
+ for z = Floor(mins.z,chunkSize), Ceil(maxs.z + 1,chunkSize), chunkSize do
+ local idx = GetZoneIndex(Vector(x, y, z))
+ zones.Map[idx] = zones.Map[idx] or {}
+ table.insert(zones.Map[idx], zone)
+ end
+ end
+ end
+ end
+end
+
+function zones.GetNearbyZones(pos)
+ //This system isn't working.
+ -- local idx = GetZoneIndex(pos)
+ -- return zones.Map[idx] or {}
+ return zones.List
+end
+
+zones.Cache = {}
+local function ClearCache()
+ for k,v in pairs(player.GetAll()) do
+ zones.Cache[v] = {}
+ end
+end
+ClearCache()
+hook.Add("Tick","zones_cache",ClearCache)
+
+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 {}
+
+ //Update legacy files:
+ for k,v in pairs(zones.List)do
+ if not v.bounds then
+ zones.CalcBounds(v)
+ end
+
+ hook.Run("OnZoneLoaded",v,v.class,k)
+ end
+ end
+
+ local sync = false
+ local syncply
+
+ function zones.Sync(ply)
+ sync = true
+ syncply = ply
+ end
+
+ hook.Add("Tick","zones_sync",function()
+ if sync then
+ net.Start("zones_sync")
+ net.WriteTable(zones.List)
+ if syncply then
+ net.Send(syncply)
+ syncply = nil
+ else
+ net.Broadcast()
+ zones.CreateZoneMapping()
+ 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(),
+ bounds = {}
+ }
+
+ local id = table.maxn(zones.List) + 1
+ local cur = ent
+ repeat
+ local pos = cur:GetPos() - Vector(0,0,2)
+ zone.points[1][#zone.points[1]+1] = pos
+
+ cur:SetZoneID(id)
+ cur = cur:GetNext()
+
+ until (cur == ent)
+
+ zones.CalcBounds(zone,true)
+
+ zones.List[id] = zone
+ hook.Run("OnZoneCreated",zone,zone.class,id)
+
+ zones.Sync()
+
+
+ return zone, id
+
+ end
+
+ function zones.CalcBounds(zone,newZone)
+ local mins,maxs = Vector(10000000,10000000,10000000), Vector(-10000000,-10000000,-10000000)
+ for areanum,area in pairs(zone.points)do
+ for k,pos in pairs(area) do
+ maxs.x = math.max(pos.x, maxs.x)
+ maxs.y = math.max(pos.y, maxs.y)
+ maxs.z = math.max(pos.z+zone.height[areanum], maxs.z)
+ mins.x = math.min(pos.x, mins.x)
+ mins.y = math.min(pos.y, mins.y)
+ mins.z = math.min(pos.z, mins.z)
+ end
+ end
+ zone.bounds = {["mins"]=mins,["maxs"]=maxs}
+ if not newZone then
+ hook.Run("OnZoneChanged",zone,zone.class,zones.GetID(zone))
+ end
+ end
+
+ function zones.Remove(id)
+ hook.Run("OnZoneRemoved",zones.List[id],zones.List[id].class,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.CalcBounds(zto)
+ zones.Remove(from)
+
+ hook.Run("OnZoneMerged",zto,zto.class,to,zfrom,zfrom.class,from)
+
+ zones.Sync()
+
+ end
+
+ function zones.Split(id,areanum)
+ local zone = zones.List[id]
+ local pts, h, bound = 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.CalcBounds(zone)
+ zones.CalcBounds(new)
+
+ hook.Run("OnZoneSplit",new,new.class,id,zone,id)
+
+ zones.Sync()
+
+ return new,id
+
+ end
+
+ function zones.ChangeClass(id,class)
+ local zone,new = zones.List[id],{}
+ new.points = zone.points
+ new.height = zone.height
+ new.bounds = zone.bounds
+ new.class = class
+
+ zones.List[id] = new
+
+ hook.Run("OnZoneCreated",new,class,id)
+
+ zones.Sync()
+ end
+
+
+ local mapMins = -16000
+ local mapMaxs = 16000
+ local mapSize = 32000
+ local chunkSize = 128
+
+ function zones.GetZoneIndex(pos)
+
+ local x = math.Remap(pos.x, mapMins, mapMaxs, 0, mapSize)
+ local y = math.Remap(pos.y, mapMins, mapMaxs, 0, mapSize)
+ local z = math.Remap(pos.z, mapMins, mapMaxs, 0, mapSize)
+
+ local idxX = math.floor(x / chunkSize)
+ local idxY = math.floor(y / chunkSize)
+ local idxZ = math.floor(z / chunkSize)
+ local idx = bit.bor(bit.lshift(idxX, 24), bit.lshift(idxY, 14), idxZ)
+
+ return idx
+
+ end
+
+ hook.Add("InitPostEntity","zones_load",function()
+ zones.LoadZones()
+ end)
+ hook.Add("PlayerInitialSpawn","zones_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.ChangeClass(id,class)
+
+ end)
+
+else
+ net.Receive("zones_sync",function(len)
+ zones.List = net.ReadTable()
+ zones.CreateZoneMapping()
+ end)
+
+ function zones.ShowOptions(id)
+
+ local zone = zones.List[id]
+ local class = zone.class
+
+ local frame = vgui.Create("DFrame")
+ zones.optionsFrame = frame
+ 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)
+ frame.content:DockPadding(5,5,5,5)
+
+ 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)
+ frame.content:DockPadding(5,5,5,5)
+
+ 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.
+
+ local ray = {
+ x1 = point.x,
+ y1 = point.y,
+ x2 = 100000,
+ y2 = 100000
+ }
+
+ local inside = false
+
+ local line = {
+ x1 = 0,
+ y1 = 0,
+ x2 = 0,
+ y2 = 0
+ }
+
+ //Perform ray test
+ for k1, v in pairs(poly) do
+
+ local v2 = poly[k1+1]
+ if not v2 then
+ v2 = poly[1]
+ end
+
+ line["x1"] = v.x
+ line["y1"] = v.y
+ line["x2"] = v2.x
+ line["y2"] = v2.y
+
+ if Intersect(ray,line) then
+ inside = !inside
+ end
+
+ end
+
+ return inside
+end