From 8a53d915a750a29cb877b21551eb2525d98e2872 Mon Sep 17 00:00:00 2001 From: Bob Blackmon Date: Sat, 1 Apr 2017 16:46:43 -0400 Subject: Initial Commit --- zones/lua/autorun/zone_example.lua | 69 +++ zones/lua/entities/ent_zone_point.lua | 132 +++++ zones/lua/weapons/weapon_zone_designator.lua | 688 +++++++++++++++++++++++++++ zones/lua/zones.lua | 451 ++++++++++++++++++ 4 files changed, 1340 insertions(+) create mode 100644 zones/lua/autorun/zone_example.lua create mode 100644 zones/lua/entities/ent_zone_point.lua create mode 100644 zones/lua/weapons/weapon_zone_designator.lua create mode 100644 zones/lua/zones.lua diff --git a/zones/lua/autorun/zone_example.lua b/zones/lua/autorun/zone_example.lua new file mode 100644 index 0000000..a583c22 --- /dev/null +++ b/zones/lua/autorun/zone_example.lua @@ -0,0 +1,69 @@ +--This is an example usage of the zones system. +AddCSLuaFile("zones.lua") +include("zones.lua") + +zones.RegisterClass("Arena Zone",Color(255,0,0)) + +--Use this to set default properties. Only called on server. +hook.Add("OnZoneCreated","Arena Zone",function(zone,class,zoneID) + if class == "Arena Zone" then + + zone.DmgMul = 1 + + end +end) + +-- Use this hook to let a player change a zone after making it or with the edit tool. +-- class is zone.class, zone is the zone's full table, DPanel is a panel to parent your things to, zoneID is the zone's ID, DFrame is the whole frame. +-- Return your preferred width and height for the panel and the frame will size to it. +hook.Add("ShowZoneOptions","Arena Zone",function(zone,class,DPanel,zoneID,DFrame) + if class == "Arena Zone" then + local w,h = 500, 400 + + local mulbl = Label("Damage Multiplier:") + mulbl:SetParent(DPanel) + mulbl:SetPos(5,5) + mulbl:SetTextColor(color_black) + mulbl:SizeToContents() + + local mul = vgui.Create("DNumberWang",DPanel) --parent to the panel. + mul:SetPos(5,mulbl:GetTall()+10) + mul:SetValue(zone.DmgMul) + mul:SetDecimals(1) + mul:SetMinMax(0,10) + function mul:OnValueChanged(new) + net.Start("arena_zone") + net.WriteFloat(zoneID) + net.WriteFloat(new) + net.SendToServer() + end + + + + return w, h -- Specify the width and height for the DPanel container. The frame will resize accordingly. + + end +end) + +if SERVER then + util.AddNetworkString("arena_zone") + net.Receive("arena_zone",function(len,ply) + local id, new = net.ReadFloat(), net.ReadFloat() + if not ply:IsAdmin() then return end + + zones.List[id].DmgMul = new + zones.Sync() + + end) +end + +hook.Add("ScalePlayerDamage","Arena Zone",function(ply, hitgroup, dmginfo) + local zone = ply:GetCurrentZone() + if zone then + if zone.class == "Arena Zone" then + print("scaling") + dmginfo:ScaleDamage(zone.DmgMul) + + end + end +end) diff --git a/zones/lua/entities/ent_zone_point.lua b/zones/lua/entities/ent_zone_point.lua new file mode 100644 index 0000000..986221a --- /dev/null +++ b/zones/lua/entities/ent_zone_point.lua @@ -0,0 +1,132 @@ + +AddCSLuaFile() + +DEFINE_BASECLASS( "base_anim" ) + +ENT.PrintName = "Zone Point" +ENT.Author = "Bobblehead" +ENT.Information = "A point in the zone designator." +ENT.Category = "Other" + +ENT.Editable = false +ENT.Spawnable = false +ENT.AdminOnly = false +ENT.RenderGroup = RENDERGROUP_TRANSLUCENT + + +function ENT:Initialize() + self:SetModel("models/hunter/blocks/cube025x025x025.mdl") + self:SetCollisionGroup(COLLISION_GROUP_WORLD) + self:DrawShadow(false) + + if SERVER then + self:SetUseType(SIMPLE_USE) + self:PhysicsInit(SOLID_BBOX) + self:GetPhysicsObject():EnableMotion(false) + + self:GetPhysicsObject():SetMass(1) + + else + self:SetRenderBoundsWS(self:GetPos(),self:GetPos()+Vector(0,0,self:GetTall())) + end +end + +function ENT:SetupDataTables() + self:NetworkVar("Entity",0,"Next") + self:NetworkVar("Float",0,"Tall") + self:NetworkVar("Int",0,"ZoneID") + self:NetworkVar("String",0,"ZoneClass") + self:NetworkVar("Int",1,"AreaNumber") +end + +function ENT:DrawTranslucent() + local wep = LocalPlayer():GetActiveWeapon() + if wep:IsValid() and wep:GetClass() == "weapon_zone_designator" then + + if wep:GetZoneClass() == self:GetZoneClass() or GetConVarNumber("zone_filter") == 0 then + self:DrawModel() + + local p = self:GetPos() + p.z = p.z + self:GetTall() + render.Model({model=self:GetModel(),pos=p,ang=angle_zero}) + + render.SetMaterial(Material("cable/cable2")) + render.DrawBeam( self:GetPos(), p, 1, 1, 0, color_white ) + + local next = self:GetNext() + if IsValid(next) then + local class = self:GetZoneClass() + + render.DrawBeam( self:GetPos(), next:GetPos(), 1, 1, 0, color_white ) + + local n = next:GetPos() + n.z = n.z + next:GetTall() + render.DrawBeam( p, n, 1, 1, 0, color_white ) + + render.SetColorMaterial() + local col1 = table.Copy(zones.Classes[class]) + col1.a = 80 + local col2 = {a=80} + col2.r = col1.r * .5 + col2.g = col1.g * .5 + col2.b = col1.b * .5 + + render.DrawQuad(p,self:GetPos(),next:GetPos(),n,col1) + render.DrawQuad(n,next:GetPos(),self:GetPos(),p,col2) + + local id = self:GetZoneID() + local classtxt = id != -1 and class .. " (# "..id..")" or class + + local ang = (p-self:GetPos()):Cross(n-self:GetPos()):Angle() + ang:RotateAroundAxis(ang:Right(), 90) + ang:RotateAroundAxis(ang:Up(),-90) + cam.Start3D2D((n+self:GetPos())/2,ang,.2) + draw.SimpleText(classtxt,"DermaLarge",0,0,color_white,TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER) + cam.End3D2D() + + ang:RotateAroundAxis(Vector(0,0,1), 180) + cam.Start3D2D((n+self:GetPos())/2,ang,.2) + draw.SimpleText(classtxt,"DermaLarge",0,0,color_white,TEXT_ALIGN_CENTER,TEXT_ALIGN_CENTER) + cam.End3D2D() + + end + end + end +end + +function ENT:Think() + if CLIENT then + local next = self:GetNext() + if IsValid(next) and next != self.resizedto then + self:SetRenderBoundsWS(self:GetPos(),next:GetPos()+Vector(0,0,next:GetTall())) + self.resizedto = next + end + + local wep = LocalPlayer():GetActiveWeapon() + if wep:IsValid() and wep:GetClass() == "weapon_zone_designator" then + if LocalPlayer():GetEyeTrace().Entity == self then + self:SetColor(Color(255,0,0)) + elseif wep:GetCurrentPoint() == self then + self:SetColor(Color(0,0,255)) + else + self:SetColor(color_white) + end + end + else + + if IsValid(self.Resizing) then + + self:SetTall((self.Resizing:GetEyeTrace().HitPos - self:GetPos()).z) + + end + + end +end + +function ENT:OnRemove() + if SERVER then + if IsValid(self:GetNext()) then + self:GetNext():Remove() + end + end +end diff --git a/zones/lua/weapons/weapon_zone_designator.lua b/zones/lua/weapons/weapon_zone_designator.lua new file mode 100644 index 0000000..cc30629 --- /dev/null +++ b/zones/lua/weapons/weapon_zone_designator.lua @@ -0,0 +1,688 @@ + +if CLIENT then + CreateClientConVar("zone_tall",200,false,true) + CreateClientConVar("zone_class","",false,true) + CreateClientConVar("zone_editmode",1,false,true) + CreateClientConVar("zone_filter",0,false,true) + + surface.CreateFont( "zones_save", { + font = "Roboto", -- Use the font-name which is shown to you by your operating system Font Viewer, not the file name + size = 20, + weight = 1000, + } ) + surface.CreateFont( "zones_screen", { + font = "Arial", -- Use the font-name which is shown to you by your operating system Font Viewer, not the file name + size = 20, + weight = 1000, + + } ) +end + +if engine.ActiveGamemode() == "terrortown" then + SWEP.Base = "weapon_tttbase" +end + + +SWEP.Kind = WEAPON_EQUIP2 + +SWEP.NoSights = true + +SWEP.PrintName = "Zone Designator" +SWEP.Author = "Bobblehead" +SWEP.Purpose = "Creates zones. Reload for menu. Right click to remove a point/zone." + +SWEP.Slot = 5 +SWEP.SlotPos = 6 + +SWEP.Spawnable = true +SWEP.AdminOnly = true + +SWEP.HoldType = "pistol" +SWEP.ViewModelFOV = 51 +SWEP.ViewModelFlip = false +SWEP.UseHands = true +SWEP.ViewModel = "models/weapons/c_toolgun.mdl" +SWEP.WorldModel = "models/weapons/c_toolgun.mdl" +SWEP.AutoSwitchFrom = true +SWEP.AutoSwitchTo = true + +SWEP.Primary.ClipSize = -1 +SWEP.Primary.DefaultClip = -1 +SWEP.Primary.Automatic = false +SWEP.Primary.Delay = .5 +SWEP.Primary.Ammo = "none" + +SWEP.Secondary.ClipSize = -1 +SWEP.Secondary.DefaultClip = -1 +SWEP.Secondary.Automatic = false +SWEP.Secondary.Ammo = "none" + + +function SWEP:SetupDataTables() + self:NetworkVar("Float",0,"Tall") + self:NetworkVar("String",0,"ZoneClass") + self:NetworkVar("Entity",0,"CurrentPoint") + self:NetworkVar("Int",0,"Mode") + +end + +function SWEP:Initialize() + + self:SetTall(150) + self:SetMode(1) + + self:SetCurrentPoint(NULL) +end + +function SWEP:Deploy() + if SERVER then + local points = ents.FindByClass("ent_zone_point") + local ct = 0 + for k,v in pairs(zones.List) do + for k2,v2 in pairs(v.points)do + for k3, v3 in pairs(v2) do + ct = ct + 1 + end + end + end + if #points != ct then + zones.CreatePointEnts(points) + end + end +end + +function SWEP:Holster() + if SERVER then + local none = true + for k, ply in pairs(player.GetAll()) do + if ply == self.Owner then continue end + local wep = ply:GetActiveWeapon() + if IsValid(wep) and wep:GetClass() == "weapon_zone_designator" then + none = false + break + end + end + if none then + for k,v in pairs(ents.FindByClass("ent_zone_point"))do + v:Remove() + end + end + + self:SetCurrentPoint(NULL) + + return true + end +end + +function SWEP:DrawHUD() + local z,id = LocalPlayer():GetCurrentZone(GetConVarNumber("zone_filter") == 1 and self:GetZoneClass() or nil) + z = z and z.class.."(# "..id..")" or "None" + draw.SimpleText("Current Zone: "..z, "DermaLarge", 100,100) +end + +function SWEP:PrimaryAttack() + + + self:SendWeaponAnim( ACT_VM_PRIMARYATTACK ) + + if SERVER then + self.Owner:EmitSound("buttons/button15.wav") + end + + self:UpdateSettings() + local mode = self:GetMode() + + if mode == 1 then //Create + if CLIENT then return end + self:PlacePoint() + elseif mode == 2 then //Merge + if CLIENT then return end + self:MergeZones() + + elseif mode == 3 then //Edit + local curr = self:GetCurrentPoint() + + if IsValid(curr) then + if CLIENT then return end + local next = curr + repeat + + next.Resizing = nil + + next = next:GetNext() + + until ( next == curr ) + + zones.List[curr:GetZoneID()].height[curr:GetAreaNumber()] = curr:GetTall() + + self:SetCurrentPoint(NULL) + + elseif CLIENT then + local tr = self.Owner:GetEyeTrace() + if !tr.HitWorld and IsValid(tr.Entity) and tr.Entity:GetClass() == "ent_zone_point" then + zones.ShowOptions(tr.Entity:GetZoneID()) + end + + end + + elseif mode == 4 then + if CLIENT then return end + + local curr = self:GetCurrentPoint() + local tr = self.Owner:GetEyeTrace() + + if !tr.HitWorld and IsValid(tr.Entity) and tr.Entity:GetClass() == "ent_zone_point" then + + self:SetCurrentPoint(tr.Entity) + + elseif IsValid(curr) then + + local id,areanum = curr:GetZoneID(), curr:GetAreaNumber() + local next = curr:GetNext() + + local new = ents.Create("ent_zone_point") + local p = curr:GetPos() + tr.HitPos.z = p.z + new:SetPos(tr.HitPos) + curr:SetNext(new) + -- self:GetCurrentPoint():DeleteOnRemove(new) + + new.LastPoint = self:GetCurrentPoint() + self:SetCurrentPoint(new) + new:SetTall(curr:GetTall()) + new:SetZoneClass(curr:GetZoneClass()) + new:SetZoneID(id) + new:SetAreaNumber(areanum) + new:Spawn() + new:SetNext(next) + next.LastPoint = new + + + local n = new + local pts = {} + repeat + pts[#pts+1] = n:GetPos() - Vector(0,0,2) + n = n:GetNext() + until (n == new) + + zones.List[id].points[areanum] = pts + zones.Sync() + + end + + end + +end + +function SWEP:SecondaryAttack() + self:SendWeaponAnim( ACT_VM_PRIMARYATTACK ) + + if CLIENT then return end + + self.Owner:EmitSound("buttons/button16.wav") + + local tr = self.Owner:GetEyeTrace() + local mode = self:GetMode() + + if mode == 1 then //delete + + if !tr.HitWorld and IsValid(tr.Entity) and tr.Entity:GetClass() == "ent_zone_point" then + local point = tr.Entity + local last = point.LastPoint + + local id = point:GetZoneID() + if id != -1 then + + if #zones.List[id].points > 1 then + id = select(2,zones.Split(id, point:GetAreaNumber())) + end + + zones.Remove(id) + + end + + point:Remove() + + self:SetCurrentPoint(last) + + elseif self:GetCurrentPoint():IsValid() then + local point, last = self:GetCurrentPoint(), self:GetCurrentPoint().LastPoint + point:Remove() + self:SetCurrentPoint(last or NULL) + end + + elseif mode == 2 then //split + + if !tr.HitWorld and IsValid(tr.Entity) and tr.Entity:GetClass() == "ent_zone_point" then + local point = tr.Entity + local id = point:GetZoneID() + + if #zones.List[id].points == 1 then + return + end + + local zone, newid = zones.Split(id, point:GetAreaNumber()) + + local next = point + repeat + + next:SetZoneID(newid) + next:SetAreaNumber(next:GetAreaNumber() - #zones.List[id].points) + + next = next:GetNext() + + until ( next == point ) + + end + + elseif mode == 3 then //resize + local curr = self:GetCurrentPoint() + + if IsValid(curr) then + + local next = curr + repeat + + next.Resizing = nil + + next = next:GetNext() + + until ( next == curr ) + + zones.List[curr:GetZoneID()].height[curr:GetAreaNumber()] = curr:GetTall() + + self:SetCurrentPoint(NULL) + + else + if !tr.HitWorld and IsValid(tr.Entity) and tr.Entity:GetClass() == "ent_zone_point" then + local next = tr.Entity + repeat + next.Resizing = self.Owner + + next = next:GetNext() + + + until ( next == tr.Entity ) + + self:SetCurrentPoint(tr.Entity) + end + end + + elseif mode == 4 then //Remove a point + + if !tr.HitWorld and IsValid(tr.Entity) and tr.Entity:GetClass() == "ent_zone_point" or IsValid(self:GetCurrentPoint()) then + + local hit = tr.Entity:IsValid() and tr.Entity or self:GetCurrentPoint() + local id,areanum = hit:GetZoneID(), hit:GetAreaNumber() + + if #zones.List[id].points[areanum] > 3 then + + local last = hit.LastPoint + local next = hit:GetNext() + + next.LastPoint = last + last:SetNext(next) + + hit:SetNext(NULL) + hit.LastPoint = nil + hit:Remove() + + local n = next + local pts = {} + repeat + pts[#pts+1] = n:GetPos() - Vector(0,0,2) + n = n:GetNext() + until (n == next) + + zones.List[id].points[areanum] = pts + zones.Sync() + + self:SetCurrentPoint(last) + + end + + end + + end + + +end + +local mx,my +if CLIENT then + mx, my = ScrW()/2, ScrH()/2 +end + +function SWEP:Reload() --show menu. + + self:UpdateSettings() + if SERVER then + if game.SinglePlayer() then + self:CallOnClient("Reload") + end + return + end + + if IsValid(self.frame) then return end + + input.SetCursorPos(mx,my) + + self:OpenMenu() + +end + +function SWEP:PlacePoint() --mode == 1 + local tr = self.Owner:GetEyeTrace() + if tr.HitWorld then + + local next = ents.Create("ent_zone_point") + + if IsValid(self:GetCurrentPoint()) then + local p = self:GetCurrentPoint():GetPos() + tr.HitPos.z = p.z + next:SetPos(tr.HitPos) + self:GetCurrentPoint():SetNext(next) + -- self:GetCurrentPoint():DeleteOnRemove(next) + else + next:SetPos(tr.HitPos+Vector(0,0,1)) + end + + next.LastPoint = self:GetCurrentPoint() + self:SetCurrentPoint(next) + next:SetTall(self:GetTall()) + next:SetZoneClass(self:GetZoneClass()) + next:SetZoneID(-1) + next:SetAreaNumber(1) + next:Spawn() + + elseif tr.Entity:IsValid() and tr.Entity:GetClass() == "ent_zone_point" and tr.Entity != self:GetCurrentPoint() then + if IsValid(self:GetCurrentPoint()) then + local next = tr.Entity + if !IsValid(next.LastPoint) then + + self:GetCurrentPoint():SetNext(next) + + if IsValid(next:GetNext()) then //we've come full circle. + + next.LastPoint = self:GetCurrentPoint() + + local id = select(2,zones.CreateZoneFromPoint(self:GetCurrentPoint())) + -- self:GetCurrentPoint():DeleteOnRemove(next) + self:SetCurrentPoint(NULL) + + local o = self.Owner + timer.Simple(.1,function() -- wait for it to sync. + if IsValid(o) then + o:SendLua("zones.ShowOptions("..id..")") + end + end) + + end + + end + end + + + end +end + +function SWEP:MergeZones() + + local tr = self.Owner:GetEyeTrace() + if tr.HitWorld then + self:SetCurrentPoint(NULL) + + elseif IsValid(tr.Entity) and tr.Entity:GetClass() == "ent_zone_point" then + + local curr = self:GetCurrentPoint() + local trent = tr.Entity + if not IsValid(curr) then + self:SetCurrentPoint(trent) + + else + //Merge the zones. + if curr:GetZoneID() != trent:GetZoneID() then + local cid = curr:GetZoneID() + + --Change the points. + for k,next in pairs(ents.FindByClass("ent_zone_point")) do + if next:GetZoneID() == cid then + + next:SetZoneID(trent:GetZoneID()) + next:SetAreaNumber(next:GetAreaNumber() + #zones.List[trent:GetZoneID()].points) + next:SetZoneClass(zones.List[trent:GetZoneID()].class) + + end + + end + + self:SetCurrentPoint(NULL) + + zones.Merge(cid,trent:GetZoneID()) --from, to + + else + self:SetCurrentPoint(trent) + + end + + end + + end +end + +function SWEP:UpdateSettings() + --load convars + local new_tall = SERVER and self.Owner:GetInfoNum("zone_tall",200) or GetConVarNumber("zone_tall") + local new_class = SERVER and self.Owner:GetInfo("zone_class") or GetConVarString("zone_class") + local new_mode = SERVER and self.Owner:GetInfoNum("zone_editmode",1) or GetConVarNumber("zone_editmode") + + if new_class == "" then + for k,v in pairs(zones.Classes)do + new_class = k + self.Owner:ConCommand('zone_class "'..k..'"') + break + end + end + + --If changed, remove current building area + if new_class != self:GetZoneClass() or new_tall != self:GetTall() or new_mode != self:GetMode() then + self:ResetTool() + end + assert(new_class != "", "No class is set for the zone designator! Did you call zones.RegisterClass()?\n") + + --apply convars + self:SetTall(new_tall) + self:SetZoneClass(new_class) + self:SetMode(new_mode) +end + +function SWEP:ResetTool() + + if self:GetMode() == 1 then + local cur = self:GetCurrentPoint() + if IsValid(cur) and cur:GetZoneID() == -1 then + + while IsValid(cur) do + local last = cur.LastPoint + cur:Remove() + cur = last + end + + end + elseif self:GetMode() == 2 then + + end + self:SetCurrentPoint(NULL) +end + +function SWEP:OpenMenu() + local zc = self:GetZoneClass() + + local frame = vgui.Create("DFrame") + self.frame = frame + frame:SetSize(300,400) + frame:Center() + frame:MakePopup() + frame:ShowCloseButton(false) + frame:SetTitle("Zone Designator Options") + function frame:Think() + if not input.IsButtonDown(_G["KEY_"..input.LookupBinding("+reload"):upper()]) then + mx,my = gui.MousePos() + self:Close() + end + end + + local ztitle = vgui.Create("DLabel",frame) + ztitle:Dock(TOP) + ztitle:DockMargin(7,0,5,0) + ztitle:SetText("Zone Class:") + ztitle:SizeToContents() + + local zclass = vgui.Create("DComboBox",frame) + zclass:Dock(TOP) + zclass:DockMargin(5,0,5,0) + for k,v in pairs(zones.Classes) do + zclass:AddChoice(k,nil,k==zc) + end + function zclass:OnSelect(i,class) + RunConsoleCommand("zone_class",class) + end + + local filter = vgui.Create("DCheckBoxLabel",frame) + filter:Dock(TOP) + filter:DockMargin(6,5,5,5) + filter:SetText("Filter zones of a different class.") + filter:SizeToContents() + filter:SetConVar("zone_filter") + + local height = vgui.Create( "DNumSlider", frame ) + height:Dock(TOP) + height:DockMargin(7,0,0,0) + height:SetText( "Zone Height:" ) + height:SetMin( 0 ) + height:SetMax( 1000 ) + height:SetDecimals( 0 ) + height:SetConVar( "zone_tall" ) + + local modetitle = vgui.Create("DLabel",frame) + modetitle:Dock(TOP) + modetitle:DockMargin(7,0,5,0) + modetitle:SetText("Tool Mode:") + modetitle:SizeToContents() + + local mode = vgui.Create("DListView",frame) + mode:Dock(TOP) + mode:DockMargin(5,0,5,5) + mode:AddColumn("Mode") + mode:AddColumn("Info"):SetFixedWidth(180) + mode:SetTall(200) + mode:SetMultiSelect(false) + + mode:AddLine("Create / Delete","Create new zones") + mode:AddLine("Merge / Split","Turn two zones into one.") + mode:AddLine("Edit / Resize","Change zone properties.") + mode:AddLine("Insert / Remove","Insert points into existing zones.") + mode:SelectItem(mode:GetLine(self:GetMode())) + function mode:OnRowSelected(id) + RunConsoleCommand("zone_editmode",id) + end + + + local save = vgui.Create("DButton",frame) + save:Dock(BOTTOM) + save:DockMargin(5,0,5,5) + save:SetText("SAVE ALL ZONES") + save:SetFont("zones_save") + save:SetTextColor(color_black) + save:SetTall(50) + function save:DoClick() + Derma_Query("Are you sure you want to save? This can't be undone.", "Save Confirmation", + "Yes", function() + RunConsoleCommand("zone_save") + end, + "No", function() + end + ) + end + + + +end + +local modes = { + { --create + "Create", + "Delete" + }, + { --merge + "Merge", + "Split" + }, + { --edit + "Edit", + "Resize" + }, + { --insert + "Insert", + "Remove" + }, +} +local lmb, rmb = Material("gui/lmb.png","unlitgeneric"), Material("gui/rmb.png","unlitgeneric") +function SWEP:DrawScreen(x,y,w,h) + local mode = self:GetMode() + local txt = modes[mode] + + draw.SimpleText(txt[1], "zones_screen", x+w/4, -h/5, _, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + draw.SimpleText(txt[2], "zones_screen", w+x-w/4, -h/5, _, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + + surface.SetDrawColor(color_white) + surface.DrawRect(x+w/2-1,y+h*.125,2,h*.75) + + surface.SetMaterial(lmb) + surface.DrawTexturedRect(x+w/4-16,y+h*.55-16,32,32) + surface.SetMaterial(rmb) + surface.DrawTexturedRect(x+w*.75-16,y+h*.55-16,32,32) + +end + +local function GetBoneOrientation(self,ent) + local bone, pos, ang + bone = ent:LookupBone("Hand") + if (!bone) then return end + pos, ang = Vector(0,0,0), Angle(0,0,0) + local m = ent:GetBoneMatrix(bone) + if (m) then + pos, ang = m:GetTranslation(), m:GetAngles() + end + if (IsValid(self.Owner) and self.Owner:IsPlayer() and + ent == self.Owner:GetViewModel() and self.ViewModelFlip) then + ang.r = -ang.r // Fixes mirrored models + end + return pos, ang +end +function SWEP:ViewModelDrawn() + local vm = self.Owner:GetViewModel() + if !IsValid(vm) then return end + + local pos, ang = GetBoneOrientation(self, vm ) + local offset = Vector(.08, -5.16, 3.43) + local offsetAng = Angle(180, 0, -46) + local size = 0.0159 + local drawpos = pos + ang:Forward() * offset.x + ang:Right() * offset.y + ang:Up() * offset.z + ang:RotateAroundAxis(ang:Up(), offsetAng.y) + ang:RotateAroundAxis(ang:Right(), offsetAng.p) + ang:RotateAroundAxis(ang:Forward(), offsetAng.r) + + cam.Start3D2D(drawpos, ang, size) + local x,y,w,h = -72,-72 + local w,h = 2*-x, 2*-y + //Draw Background: + surface.SetDrawColor( 200, 200, 200, 255 ) + surface.SetDrawColor( 40,40,40,255 ) + surface.DrawRect( x, y, w, h ) + + //Draw foreground: + self:DrawScreen(x,y,w,h) + + cam.End3D2D() +end \ No newline at end of file diff --git a/zones/lua/zones.lua b/zones/lua/zones.lua new file mode 100644 index 0000000..7220688 --- /dev/null +++ b/zones/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 292)+8,(h or 422)+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 292)+8,(h or 422)+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, + intersects = 0 + } + + //Do ray check. + for k,v in pairs(lines)do + + if Intersect(ray,v) then + ray.intersects = ray.intersects + 1 + end + + end + + return (ray.intersects % 2) == 1 +end -- cgit v1.2.3-70-g09d2