445 lines
13 KiB
Lua
445 lines
13 KiB
Lua
AddCSLuaFile()
|
|
DEFINE_BASECLASS( "base_gmodentity" )
|
|
ENT.Type = "anim"
|
|
ENT.PrintName = "Wire Unnamed Ent"
|
|
ENT.Purpose = "Base for all wired SEnts"
|
|
ENT.RenderGroup = RENDERGROUP_OPAQUE
|
|
ENT.Spawnable = false
|
|
ENT.AdminOnly = false
|
|
|
|
ENT.IsWire = true
|
|
|
|
if CLIENT then
|
|
local wire_drawoutline = CreateClientConVar("wire_drawoutline", 1, true, false)
|
|
|
|
function ENT:Initialize()
|
|
self.NextRBUpdate = CurTime() + 0.25
|
|
end
|
|
|
|
function ENT:Draw()
|
|
self:DoNormalDraw()
|
|
Wire_Render(self)
|
|
if self.GetBeamLength and (not self.GetShowBeam or self:GetShowBeam()) then
|
|
-- Every SENT that has GetBeamLength should draw a tracer. Some of them have the GetShowBeam boolean
|
|
Wire_DrawTracerBeam( self, 1, self.GetBeamHighlight and self:GetBeamHighlight() or false )
|
|
end
|
|
end
|
|
|
|
local WorldTip = { dietime = 0 }
|
|
function ENT:AddWorldTip( txt )
|
|
WorldTip.dietime = SysTime() + RealFrameTime() * 4
|
|
WorldTip.ent = self
|
|
end
|
|
|
|
local edgesize = 18
|
|
|
|
-- makes sure the overlay doesn't go out of the screen & provides several useful sizes and positions for the DrawBody function
|
|
function ENT:GetWorldTipPositions( w, h, w_body, h_body, w_footer, h_footer )
|
|
local pos = LocalPlayer():GetEyeTrace().HitPos
|
|
local spos = LocalPlayer():GetShootPos()
|
|
if pos == spos then -- if the position is right in your face, get a better position
|
|
pos = spos + LocalPlayer():GetAimVector() * 5
|
|
end
|
|
pos = pos:ToScreen()
|
|
|
|
pos.x = math.Round(pos.x)
|
|
pos.y = math.Round(pos.y)
|
|
|
|
w = math.min( w, ScrW() - 64 )
|
|
h = math.min( h, ScrH() - 64 )
|
|
|
|
local maxx = pos.x - 32
|
|
local maxy = pos.y - 32
|
|
|
|
local minx = maxx - w
|
|
local miny = maxy - h
|
|
|
|
if minx < 32 then
|
|
maxx = 32 + w
|
|
minx = 32
|
|
end
|
|
|
|
if miny < 32 then
|
|
maxy = 32 + h
|
|
miny = 32
|
|
end
|
|
|
|
local centerx = (maxx+minx)/2
|
|
local centery = (maxy+miny)/2
|
|
|
|
return { min = {x = minx,y = miny},
|
|
max = {x = maxx,y = maxy},
|
|
center = {x = centerx, y = centery},
|
|
size = {w = w, h = h},
|
|
bodysize = {w = w_body, h = h_body },
|
|
footersize = {w = w_footer, h = h_footer},
|
|
edgesize = edgesize
|
|
}
|
|
end
|
|
|
|
-- This is overridable by other wire entities which want to customize the overlay, but generally you shouldn't override it
|
|
function ENT:DrawWorldTipOutline( pos )
|
|
draw.NoTexture()
|
|
surface.SetDrawColor(Color(25,25,25,200))
|
|
|
|
local poly = {
|
|
{x = pos.min.x + edgesize, y = pos.min.y, u = 0, v = 0 },
|
|
{x = pos.max.x, y = pos.min.y, u = 0, v = 0 },
|
|
{x = pos.max.x, y = pos.max.y - edgesize + 0.5, u = 0, v = 0 },
|
|
{x = pos.max.x - edgesize + 0.5, y = pos.max.y, u = 0, v = 0 },
|
|
{x = pos.min.x, y = pos.max.y, u = 0, v = 0 },
|
|
{x = pos.min.x, y = pos.min.y + edgesize, u = 0, v = 0 },
|
|
}
|
|
|
|
render.CullMode(MATERIAL_CULLMODE_CCW)
|
|
surface.DrawPoly( poly )
|
|
|
|
surface.SetDrawColor(Color(0,0,0,255))
|
|
|
|
for i=1,#poly-1 do
|
|
surface.DrawLine( poly[i].x, poly[i].y, poly[i+1].x, poly[i+1].y )
|
|
end
|
|
surface.DrawLine( poly[#poly].x, poly[#poly].y, poly[1].x, poly[1].y )
|
|
end
|
|
|
|
local function getWireName( ent )
|
|
local name = ent:GetNWString("WireName")
|
|
if not name or name == "" then return ent.PrintName else return name end
|
|
end
|
|
|
|
-- This is overridable by other wire entities which want to customize the overlay
|
|
function ENT:GetWorldTipBodySize()
|
|
local txt = self:GetOverlayData().txt
|
|
if txt == nil or txt == "" then return 0,0 end
|
|
return surface.GetTextSize( txt )
|
|
end
|
|
|
|
-- This is overridable by other wire entities which want to customize the overlay
|
|
function ENT:DrawWorldTipBody( pos )
|
|
local data = self:GetOverlayData()
|
|
draw.DrawText( data.txt, "GModWorldtip", pos.center.x, pos.min.y + edgesize/2, Color(255,255,255,255), TEXT_ALIGN_CENTER )
|
|
end
|
|
|
|
-- This is overridable by other wire entities which want to customize the overlay
|
|
function ENT:DrawWorldTip()
|
|
local data = self:GetOverlayData()
|
|
if not data then return end
|
|
|
|
surface.SetFont( "GModWorldtip" )
|
|
|
|
local txt = data.txt
|
|
local class = getWireName( self ) .. " [" .. self:EntIndex() .. "]"
|
|
local name = "(" .. self:GetPlayerName() .. ")"
|
|
|
|
local w_body, h_body = self:GetWorldTipBodySize()
|
|
local w_class, h_class = surface.GetTextSize( class )
|
|
local w_name, h_name = surface.GetTextSize( name )
|
|
|
|
local w_total = txt ~= "" and w_body or 0
|
|
local h_total = txt ~= "" and h_body or 0
|
|
|
|
local w_footer, h_footer = 0, 0
|
|
|
|
local info_requires_multiline = false
|
|
if w_total < w_class + w_name - edgesize then
|
|
info_requires_multiline = true
|
|
|
|
w_footer = math.max(w_total,w_class,w_name)
|
|
h_footer = h_class + h_name + edgesize + 8
|
|
|
|
w_total = w_footer
|
|
h_total = h_total + h_footer
|
|
else
|
|
w_footer = math.max(w_total,w_class + 8 + w_name)
|
|
h_footer = math.max(h_class,h_name) + edgesize + 8
|
|
|
|
w_total = w_footer
|
|
h_total = h_total + h_footer
|
|
end
|
|
|
|
if h_body == 0 then h_total = h_total - h_body - edgesize end
|
|
|
|
local pos = self:GetWorldTipPositions( w_total + edgesize*2,h_total + edgesize,
|
|
w_body,h_body,
|
|
w_footer,h_footer )
|
|
|
|
self:DrawWorldTipOutline( pos )
|
|
|
|
local offset = pos.min.y
|
|
if h_body > 0 then
|
|
self:DrawWorldTipBody( pos )
|
|
offset = offset + h_body + edgesize
|
|
|
|
surface.SetDrawColor( Color(0,0,0,255) )
|
|
surface.DrawLine( pos.min.x, offset, pos.max.x, offset )
|
|
end
|
|
|
|
if info_requires_multiline then
|
|
draw.DrawText( class, "GModWorldtip", pos.center.x, offset + 8, Color(255,255,255,255), TEXT_ALIGN_CENTER )
|
|
draw.DrawText( name, "GModWorldtip", pos.center.x, offset + h_class + 16, Color(255,255,255,255), TEXT_ALIGN_CENTER )
|
|
else
|
|
draw.DrawText( class, "GModWorldtip", pos.min.x + edgesize, offset + 16, Color(255,255,255,255) )
|
|
draw.DrawText( name, "GModWorldtip", pos.min.x + pos.size.w - w_name - edgesize, offset + 16, Color(255,255,255,255) )
|
|
end
|
|
end
|
|
|
|
hook.Add("HUDPaint","wire_draw_world_tips",function()
|
|
if SysTime() > WorldTip.dietime then return end
|
|
|
|
local ent = WorldTip.ent
|
|
if not IsValid(ent) then return end
|
|
|
|
ent:DrawWorldTip()
|
|
end)
|
|
|
|
-- Custom better version of this base_gmodentity function
|
|
function ENT:BeingLookedAtByLocalPlayer()
|
|
local trace = LocalPlayer():GetEyeTrace()
|
|
|
|
if trace.Entity ~= self then return false end
|
|
if trace.HitPos:Distance(LocalPlayer():GetShootPos()) > 200 then return false end
|
|
|
|
return true
|
|
end
|
|
|
|
local drawInfo = false
|
|
hook.Add('Think', 'wire.drawInfo', function()
|
|
local ply = LocalPlayer()
|
|
if not IsValid(ply) then return end
|
|
|
|
local wep = ply:GetActiveWeapon()
|
|
drawInfo = IsValid(wep) and wep:GetClass() == 'gmod_tool'
|
|
end)
|
|
|
|
function ENT:DoNormalDraw(nohalo, notip)
|
|
if not drawInfo then
|
|
return self:DrawModel()
|
|
end
|
|
|
|
local looked_at = self:BeingLookedAtByLocalPlayer()
|
|
if not nohalo and wire_drawoutline:GetBool() and looked_at then
|
|
if self.RenderGroup == RENDERGROUP_OPAQUE then
|
|
self.OldRenderGroup = self.RenderGroup
|
|
self.RenderGroup = RENDERGROUP_TRANSLUCENT
|
|
end
|
|
self:DrawEntityOutline()
|
|
self:DrawModel()
|
|
else
|
|
if self.OldRenderGroup then
|
|
self.RenderGroup = self.OldRenderGroup
|
|
self.OldRenderGroup = nil
|
|
end
|
|
self:DrawModel()
|
|
end
|
|
if not notip and looked_at then
|
|
self:AddWorldTip()
|
|
end
|
|
end
|
|
|
|
-- function ENT:Think()
|
|
-- if (CurTime() >= (self.NextRBUpdate or 0)) then
|
|
-- -- We periodically update the render bounds every 10 seconds - the
|
|
-- -- reasons why are mostly anecdotal, but in some circumstances
|
|
-- -- entities might 'forget' their renderbounds. Nobody really knows
|
|
-- -- if this is still needed or not.
|
|
-- self.NextRBUpdate = CurTime() + 10
|
|
-- Wire_UpdateRenderBounds(self)
|
|
-- end
|
|
-- end
|
|
|
|
local halos = {}
|
|
local halos_inv = {}
|
|
|
|
function ENT:DrawEntityOutline()
|
|
if halos_inv[self] then return end
|
|
halos[#halos+1] = self
|
|
halos_inv[self] = true
|
|
end
|
|
|
|
hook.Add("PreDrawHalos", "Wiremod_overlay_halos", function()
|
|
if #halos == 0 then return end
|
|
halo.Add(halos, Color(100,100,255), 3, 3, 1, true, true)
|
|
halos = {}
|
|
halos_inv = {}
|
|
end)
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Overlay getting
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- Basic legacy GetOverlayText, is no longer used here but we leave it here in case other addons rely on it.
|
|
function ENT:GetOverlayText()
|
|
local name = self:GetNWString("WireName")
|
|
if name == "" then name = self.PrintName end
|
|
local header = "- " .. name .. " -"
|
|
|
|
local data = self:GetOverlayData()
|
|
if data and data.txt then
|
|
return header .. "\n" .. data.txt
|
|
else
|
|
return header
|
|
end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Overlay receiving
|
|
--------------------------------------------------------------------------------
|
|
net.Receive( "wire_overlay_data", function( len )
|
|
local ent = net.ReadEntity()
|
|
if IsValid( ent ) then
|
|
ent.OverlayData = net.ReadTable()
|
|
end
|
|
end )
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Overlay setting
|
|
--------------------------------------------------------------------------------
|
|
-- We want more fine-grained control over everything related to overlays,
|
|
-- so we have a custom system here
|
|
|
|
-- It allows us to optionally send values rather than entire strings, which saves networking
|
|
-- It also allows us to only update overlays when someone is looking at the entity.
|
|
|
|
function ENT:SetOverlayText( txt )
|
|
if not self.OverlayData then
|
|
self.OverlayData = {}
|
|
end
|
|
|
|
if txt and #txt > 12000 then
|
|
txt = string.sub(txt,1,12000) -- I have tested this and 12000 chars is enough to cover the entire screen at 1920x1080. You're unlikely to need more
|
|
end
|
|
|
|
self.OverlayData.txt = txt
|
|
|
|
if not self.OverlayData_UpdateTime then self.OverlayData_UpdateTime = {} end
|
|
self.OverlayData_UpdateTime.time = CurTime()
|
|
end
|
|
|
|
function ENT:SetOverlayData( data )
|
|
self.OverlayData = data
|
|
if self.OverlayData.txt and #self.OverlayData.txt > 12000 then
|
|
self.OverlayData.txt = string.sub(self.OverlayData.txt,1,12000)
|
|
end
|
|
|
|
if not self.OverlayData_UpdateTime then self.OverlayData_UpdateTime = {} end
|
|
self.OverlayData_UpdateTime.time = CurTime()
|
|
end
|
|
|
|
function ENT:GetOverlayData()
|
|
return self.OverlayData
|
|
end
|
|
|
|
if CLIENT then return end -- no more client
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Overlay syncing
|
|
--------------------------------------------------------------------------------
|
|
|
|
util.AddNetworkString( "wire_overlay_data" )
|
|
|
|
timer.Create("WireOverlayUpdate", 0.1, 0, function()
|
|
for _, ply in ipairs(player.GetAll()) do
|
|
local ent = ply:GetEyeTrace().Entity
|
|
if IsValid(ent) and ent.IsWire and
|
|
ent.OverlayData and
|
|
ent.OverlayData_UpdateTime and
|
|
ent.OverlayData_UpdateTime.time > (ent.OverlayData_UpdateTime[ply] or 0) then
|
|
|
|
ent.OverlayData_UpdateTime[ply] = CurTime()
|
|
|
|
net.Start( "wire_overlay_data" )
|
|
net.WriteEntity( ent )
|
|
net.WriteTable( ent.OverlayData )
|
|
net.Send( ply )
|
|
end
|
|
end
|
|
end)
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Other functions
|
|
--------------------------------------------------------------------------------
|
|
|
|
function ENT:Initialize()
|
|
BaseClass.Initialize(self)
|
|
self:PhysicsInit(SOLID_VPHYSICS)
|
|
self:SetMoveType(MOVETYPE_VPHYSICS)
|
|
self:SetSolid(SOLID_VPHYSICS)
|
|
self.WireDebugName = self.WireDebugName or (self.PrintName and self.PrintName:sub(6)) or self:GetClass():gsub("gmod_wire", "")
|
|
end
|
|
|
|
function ENT:OnRemove()
|
|
WireLib.Remove(self)
|
|
end
|
|
|
|
function ENT:OnRestore()
|
|
WireLib.Restored(self)
|
|
end
|
|
|
|
function ENT:BuildDupeInfo()
|
|
return WireLib.BuildDupeInfo(self)
|
|
end
|
|
|
|
function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID)
|
|
WireLib.ApplyDupeInfo(ply, ent, info, GetEntByID)
|
|
end
|
|
|
|
function ENT:PreEntityCopy()
|
|
duplicator.ClearEntityModifier(self, "WireDupeInfo")
|
|
-- build the DupeInfo table and save it as an entity mod
|
|
local DupeInfo = self:BuildDupeInfo()
|
|
if DupeInfo then
|
|
duplicator.StoreEntityModifier(self, "WireDupeInfo", DupeInfo)
|
|
end
|
|
end
|
|
|
|
function ENT:OnEntityCopyTableFinish(dupedata)
|
|
-- Called by Garry's duplicator, to modify the table that will be saved about an ent
|
|
|
|
-- Remove anything with non-string keys, or util.TableToJSON will crash the game
|
|
dupedata.OverlayData_UpdateTime = nil
|
|
end
|
|
|
|
local function EntityLookup(CreatedEntities)
|
|
return function(id, default)
|
|
if id == nil then return default end
|
|
if id == 0 then return game.GetWorld() end
|
|
local ent = CreatedEntities[id]
|
|
if IsValid(ent) then return ent else return default end
|
|
end
|
|
end
|
|
|
|
function ENT:OnDuplicated()
|
|
self.DuplicationInProgress = true
|
|
end
|
|
|
|
function ENT:PostEntityPaste(Player,Ent,CreatedEntities)
|
|
-- We manually apply the entity mod here rather than using a
|
|
-- duplicator.RegisterEntityModifier because we need access to the
|
|
-- CreatedEntities table.
|
|
if Ent.EntityMods and Ent.EntityMods.WireDupeInfo then
|
|
Ent:ApplyDupeInfo(Player, Ent, Ent.EntityMods.WireDupeInfo, EntityLookup(CreatedEntities))
|
|
end
|
|
self.DuplicationInProgress = nil
|
|
end
|
|
|
|
-- Helper function for entities that can be linked
|
|
ENT.LINK_STATUS_UNLINKED = 1
|
|
ENT.LINK_STATUS_LINKED = 2
|
|
ENT.LINK_STATUS_INACTIVE = 2 -- alias
|
|
ENT.LINK_STATUS_DEACTIVATED = 2 -- alias
|
|
ENT.LINK_STATUS_ACTIVE = 3
|
|
ENT.LINK_STATUS_ACTIVATED = 3 -- alias
|
|
function ENT:ColorByLinkStatus(status)
|
|
local a = self:GetColor().a
|
|
|
|
if status == self.LINK_STATUS_UNLINKED then
|
|
self:SetColor(Color(255,0,0,a))
|
|
elseif status == self.LINK_STATUS_LINKED then
|
|
self:SetColor(Color(255,165,0,a))
|
|
elseif status == self.LINK_STATUS_ACTIVE then
|
|
self:SetColor(Color(0,255,0,a))
|
|
else
|
|
self:SetColor(Color(255,255,255,a))
|
|
end
|
|
end
|