dobrograd-13-06-2022/garrysmod/addons/feature-wire/lua/entities/gmod_wire_cameracontroller.lua
Jonny_Bro (Nikita) e4d5311906 first commit
2023-11-16 15:01:19 +05:00

1089 lines
28 KiB
Lua

AddCSLuaFile()
DEFINE_BASECLASS( "base_wire_entity" )
ENT.PrintName = "Wire Camera Controller"
ENT.WireDebugName = "Camera Controller"
if CLIENT then
--------------------------------------------------
-- Camera controller
-- Clientside
--------------------------------------------------
local enabled = false
local self
local clientprop
-- Position
local pos = Vector(0,0,0)
local smoothpos = Vector(0,0,0)
-- Distance & zooming
local distance = 0
local curdistance = 0
local oldcurdistance = 0
local smoothdistance = 0
local zoomdistance = 0
local zoombind = 0
-- Angle
local ang = Angle(0,0,0)
local smoothang = Angle(0,0,0)
local oldeyeang = Angle(0,0,0)
local unroll = false
-- Options
local ParentLocal = false
local AutoMove = false
local FreeMove = false
local LocalMove = false
local AutoUnclip = false
local AutoUnclip_IgnoreWater = false
local AllowZoom = false
local DrawPlayer = true
local DrawParent = true
-- View calculations
local resetViewAngles = false
local max = math.max
local abs = math.abs
local pos_speed_convar = GetConVar( "wire_cam_smooth_amount" )
local Parent
local function GetParent()
return Parent, IsValid( Parent )
end
local function DoAutoMove( curpos, curang, curdistance, parent, HasParent )
local pos_speed = pos_speed_convar:GetFloat()
local ang_speed = pos_speed - 2
curang = LocalPlayer():EyeAngles()
if AllowZoom then
if zoombind ~= 0 then
zoomdistance = math.Clamp(zoomdistance + zoombind * FrameTime() * 100 * max((abs(curdistance) + abs(zoomdistance))/10,10),0,16000-curdistance)
zoombind = 0
end
curdistance = curdistance + zoomdistance
end
smoothdistance = Lerp( FrameTime() * pos_speed, smoothdistance, curdistance )
if HasParent then
if LocalMove then
curpos = parent:LocalToWorld( curpos - curang:Forward() * smoothdistance )
curang = parent:LocalToWorldAngles( curang )
else
curpos = parent:LocalToWorld( curpos ) - curang:Forward() * smoothdistance
end
else
curpos = curpos - curang:Forward() * smoothdistance
end
return curpos, curang
end
local function DoAutoUnclip( curpos, parent, HasParent )
local start, endpos
if not AutoMove then
if HasParent then
start = parent:GetPos()
else
start = self:GetPos()
end
endpos = curpos
else
if HasParent then
start = parent:LocalToWorld(pos)
else
start = pos
end
endpos = curpos
end
local tr = {
start = start,
endpos = endpos,
mask = (AutoUnclip_IgnoreWater and CONTENTS_SOLID or bit.bor(MASK_WATER, CONTENTS_SOLID)),
mins = Vector(-8,-8,-8),
maxs = Vector(8,8,8)
}
local trace = util.TraceHull( tr )
if trace.Hit then
return trace.HitPos
end
return curpos
end
hook.Remove("CalcView","wire_camera_controller_calcview")
hook.Add( "CalcView", "wire_camera_controller_calcview", function()
if not enabled then return end
if not IsValid( self ) then enabled = false return end
local pos_speed = pos_speed_convar:GetFloat()
local ang_speed = pos_speed - 2
local curpos = pos
local curang = ang
local curdistance = distance
local parent, HasParent = GetParent()
local newview = {}
-- AutoMove
if AutoMove then
-- only smooth the position, and do it before the automove
smoothpos = LerpVector( FrameTime() * pos_speed, smoothpos, curpos )
curpos, curang = DoAutoMove( smoothpos, curang, curdistance, parent, HasParent )
if AutoUnclip then
curpos = DoAutoUnclip( curpos, parent, HasParent )
end
-- apply view
newview.origin = curpos
newview.angles = curang
elseif HasParent then
-- smooth BEFORE using toWorld
smoothpos = LerpVector( FrameTime() * pos_speed, smoothpos, curpos )
smoothang = LerpAngle( FrameTime() * ang_speed, smoothang, curang )
-- now toworld it
curpos = parent:LocalToWorld( smoothpos )
curang = parent:LocalToWorldAngles( smoothang )
-- now check for auto unclip
if AutoUnclip then
curpos = DoAutoUnclip( curpos, parent, HasParent )
end
-- apply view
newview.origin = curpos
newview.angles = curang
else
-- check auto unclip first
if AutoUnclip then
curpos = DoAutoUnclip( curpos, parent, HasParent )
end
-- there's no parent, just smooth it
smoothpos = LerpVector( FrameTime() * pos_speed, smoothpos, curpos )
smoothang = LerpAngle( FrameTime() * ang_speed, smoothang, curang )
newview.origin = smoothpos
newview.angles = smoothang
end
newview.drawviewer = DrawPlayer -- this doesn't work (probably because I use SetViewEntity serverside)
return newview
end)
hook.Add("PlayerBindPress", "wire_camera_controller_zoom", function(ply, bind, pressed)
if enabled and AllowZoom then
if (bind == "invprev") then
zoombind = -1
return true
elseif (bind == "invnext") then
zoombind = 1
return true
end
end
end)
local mouse_sensitvity = GetConVar("sensitivity")
hook.Remove("InputMouseApply", "wire_camera_controller_input_unlock")
hook.Add("InputMouseApply", "wire_camera_controller_input_unlock", function(cmd, x, y, ang)
if resetViewAngles then
cmd:SetViewAngles( Angle(0, ang.y, 0) )
resetViewAngles = false
return true
end
if not enabled then return end
if not FreeMove then return end
if not IsValid( self ) then enabled = false return end
-- feels correct, might not be, but raw values were definitely too fast
local smooth = mouse_sensitvity:GetFloat() * FrameTime()
local matrix = Matrix()
matrix:SetAngles( ang )
-- could make this a number instead
if unroll then
local parent, hasParent = GetParent()
if hasParent then
local parentMatrix = Matrix()
parentMatrix:SetAngles( parent:GetAngles() )
local diffAngles = (matrix:GetInverseTR() * parentMatrix):GetAngles()
if math.abs(diffAngles.y) > 90 then diffAngles.r = -diffAngles.r end
matrix:Rotate( Angle( y * smooth, -x * smooth, diffAngles.r * smooth ) )
else
matrix:Rotate( Angle( y * smooth, -x * smooth, -ang.r * smooth ) )
end
else
matrix:Rotate( Angle( y * smooth, -x * smooth, 0 ) )
end
cmd:SetViewAngles( matrix:GetAngles() )
return true
end)
--------------------------------------------------
-- Receiving data from server
--------------------------------------------------
local WaitingForID
local function ReadPositions()
-- pos/ang
pos.x = net.ReadFloat()
pos.y = net.ReadFloat()
pos.z = net.ReadFloat()
ang.p = net.ReadFloat()
ang.y = net.ReadFloat()
ang.r = net.ReadFloat()
unroll = net.ReadBit() ~= 0
-- distance
distance = math.Clamp(net.ReadFloat(),-16000,16000)
-- Parent
WaitingForID = net.ReadInt(32)
if WaitingForID ~= -1 and IsValid( Entity(WaitingForID) ) then
Parent = Entity(WaitingForID)
WaitingForID = nil
elseif WaitingForID == -1 then
WaitingForID = nil
end
end
-- if the camera is parented to an entity that was very recently
-- created, there is a chance it doesn't exist on the client yet,
-- so we use this hook to wait for it to be created
hook.Add( "NetworkEntityCreated", "wire_camera_controller_network_entity", function( ent )
if WaitingForID and ent:EntIndex() == WaitingForID then
Parent = ent
WaitingForID = nil
end
end)
net.Receive( "wire_camera_controller_toggle", function( len )
local enable = net.ReadBit() ~= 0
local cam = net.ReadEntity()
if cam ~= self and enabled then return end -- another camera controller is already enabled
self = cam
-- make the previous parent visible
-- (this also makes the parent visible when you turn off the cam controller)
local parent, HasParent = GetParent()
if HasParent then
parent:SetNoDraw( false )
end
if enable then
ParentLocal = net.ReadBit() ~= 0
AutoMove = net.ReadBit() ~= 0
FreeMove = net.ReadBit() ~= 0
LocalMove = net.ReadBit() ~= 0
AllowZoom = net.ReadBit() ~= 0
AutoUnclip = net.ReadBit() ~= 0
AutoUnclip_IgnoreWater = net.ReadBit() ~= 0
DrawPlayer = net.ReadBit() ~= 0
DrawParent = net.ReadBit() ~= 0
ReadPositions()
-- Hide the parent if that's what the user wants
local parent, HasParent = GetParent()
if HasParent and not DrawParent then
parent:SetNoDraw( true )
end
-- If we switched on, set current positions and angles
if not enabled then
-- Copy them
smoothpos = Vector(pos.x,pos.y,pos.z)
smoothang = Angle(ang.p,ang.y,ang.r)
curdistance = distance
smoothdistance = distance
zoomdistance = 0
end
else
WaitingForID = nil
resetViewAngles = true
end
enabled = enable
end)
net.Receive( "wire_camera_controller_sync", function( len )
if not enabled or not IsValid( self ) then return end
local cam = net.ReadEntity()
if cam ~= self then return end -- another cam controller is trying to hijack us
ReadPositions()
end)
return -- No more client
end
--------------------------------------------------
-- Initialize
--------------------------------------------------
function ENT:Initialize()
BaseClass.Initialize(self)
self.Outputs = WireLib.CreateOutputs( self, { "On", "HitPos [VECTOR]", "CamPos [VECTOR]", "CamDir [VECTOR]",
"CamAng [ANGLE]", "Trace [RANGER]" } )
self.Inputs = WireLib.CreateInputs( self, { "Activated", "Direction [VECTOR]", "Angle [ANGLE]", "Position [VECTOR]",
"Distance", "UnRoll", "Parent [ENTITY]", "FilterEntities [ARRAY]", "FLIR", "FOV" } )
self.Activated = false -- Whether or not to activate the cam controller for all players sitting in linked vehicles, or as soon as a player sits in a linked vehicle
self.Active = false -- Whether the player is currently being shown the camera view.
self.FOV = nil -- The FOV of the player's view. (By default, do not change the FOV.)
self.FLIR = false -- Whether infrared view is turned on.
self.Position = Vector(0,0,0)
self.Angle = Angle(0,0,0)
self.Distance = 0
self.UnRoll = false
self.Players = {}
self.Vehicles = {}
self.NextGetContraption = 0
self.NextUpdateOutputs = 0
self:GetContraption()
self:ColorByLinkStatus(self.LINK_STATUS_UNLINKED)
end
--------------------------------------------------
-- UpdateOverlay
--------------------------------------------------
function ENT:UpdateOverlay()
local unclip = self.AutoUnclip and "Yes" or "No"
if self.AutoUnclip_IgnoreWater then unclip = unclip .. " (Ignores water)" end
self:SetOverlayText(
string.format( "Local Coordinates: %s\nClient side movement: %s\nFree movement: %s\nCL movement local to parent: %s\nClient side zooming: %s\nAuto unclip: %s\nDraw player: %s\nDraw parent: %s\n\nActivated: %s",
self.ParentLocal and "Yes" or "No",
self.AutoMove and "Yes" or "No",
self.FreeMove and "Yes" or "No",
self.LocalMove and "Yes" or "No",
self.AllowZoom and "Yes" or "No",
unclip,
self.DrawPlayer and "Yes" or "No",
self.DrawParent and "Yes" or "No",
self.Activated and "Yes" or "No"
)
)
end
--------------------------------------------------
-- Setup
--------------------------------------------------
function ENT:Setup(ParentLocal,AutoMove,FreeMove,LocalMove,AllowZoom,AutoUnclip,DrawPlayer,AutoUnclip_IgnoreWater,DrawParent)
self.ParentLocal = tobool(ParentLocal)
self.AutoMove = tobool(AutoMove)
self.FreeMove = tobool(FreeMove)
self.LocalMove = tobool(LocalMove)
self.AllowZoom = tobool(AllowZoom)
self.AutoUnclip = tobool(AutoUnclip)
self.AutoUnclip_IgnoreWater = tobool(AutoUnclip_IgnoreWater)
self.DrawPlayer = tobool(DrawPlayer)
self.DrawParent = tobool(DrawParent)
self:UpdateOverlay()
end
--------------------------------------------------
-- Data sending
--------------------------------------------------
local function SendPositions( pos, ang, dist, parent, unroll )
-- pos/ang
net.WriteFloat( pos.x )
net.WriteFloat( pos.y )
net.WriteFloat( pos.z )
net.WriteFloat( ang.p )
net.WriteFloat( ang.y )
net.WriteFloat( ang.r )
net.WriteBit( unroll )
-- distance
net.WriteFloat( dist )
-- parent
local id = IsValid( parent ) and parent:EntIndex() or -1
net.WriteInt( id, 32 )
end
util.AddNetworkString( "wire_camera_controller_toggle" )
function ENT:SyncSettings( ply, active )
if active == nil then active = self.Active end
if not IsValid(ply) then ply = self.Players end
net.Start( "wire_camera_controller_toggle" )
net.WriteBit( active )
net.WriteEntity( self )
if self.Active then
net.WriteBit( self.ParentLocal )
net.WriteBit( self.AutoMove )
net.WriteBit( self.FreeMove )
net.WriteBit( self.LocalMove )
net.WriteBit( self.AllowZoom )
net.WriteBit( self.AutoUnclip )
net.WriteBit( self.AutoUnclip_IgnoreWater )
net.WriteBit( self.DrawPlayer )
net.WriteBit( self.DrawParent )
SendPositions( self.Position, self.Angle, self.Distance, self.Parent, self.UnRoll )
end
net.Send( ply )
end
util.AddNetworkString( "wire_camera_controller_sync" )
function ENT:SyncPositions( ply )
if not IsValid(ply) then ply = self.Players end
net.Start( "wire_camera_controller_sync" )
net.WriteEntity( self )
SendPositions( self.Position, self.Angle, self.Distance, self.Parent, self.UnRoll )
net.Send( ply )
end
--------------------------------------------------
-- GetContraption
-- Used in UpdateOutputs to make the traces ignore the contraption
--------------------------------------------------
function ENT:GetContraption()
if CurTime() < self.NextGetContraption then return end
self.NextGetContraption = CurTime() + 5
self.Entities = {}
local parent = self
if IsValid( self.Parent ) then parent = self.Parent end
local ents = constraint.GetAllConstrainedEntities( parent )
for k,v in pairs( ents ) do
self.Entities[#self.Entities+1] = v
end
if self.Inputs.FilterEntities and self.Inputs.FilterEntities.Value then
for k,v in pairs( self.Inputs.FilterEntities.Value ) do
if IsEntity( v ) and IsValid( v ) then
self.Entities[#self.Entities+1] = v
end
end
end
end
--------------------------------------------------
-- UpdateOutputs
--------------------------------------------------
function ENT:UpdateOutputs()
if CurTime() < self.NextUpdateOutputs then return end
self.NextUpdateOutputs = CurTime() + 0.1
local ply = self.Players[1]
if self.Active and IsValid( ply ) then
local parent = self.Parent
local HasParent = IsValid( parent )
local pos, ang = self.Position, self.Angle
local curpos = pos
local curang = ang
if self.AutoMove then
curang = ply:EyeAngles()
local veh = ply:GetVehicle()
if IsValid( veh ) then curang = veh:WorldToLocalAngles( curang ) end
local dist = self.Distance
if HasParent and IsValid( parent ) then
if self.LocalMove then
curpos = parent:LocalToWorld( curpos - curang:Forward() * dist )
curang = parent:LocalToWorldAngles( curang )
else
curpos = parent:LocalToWorld( curpos ) - curang:Forward() * dist
end
else
curpos = curpos - curang:Forward() * dist
end
else
if HasParent then
curpos = parent:LocalToWorld( curpos )
curang = parent:LocalToWorldAngles( curang )
end
end
-- AutoUnclip
if self.AutoUnclip then
local start, endpos
if not self.AutoMove then
if HasParent then
start = parent:GetPos()
else
start = self:GetPos()
end
endpos = curpos
else
if HasParent then
start = parent:LocalToWorld(pos)
else
start = pos
end
endpos = curpos
end
local tr = {
start = start,
endpos = endpos,
mask = (self.AutoUnclip_IgnoreWater and CONTENTS_SOLID or bit.bor(MASK_WATER, CONTENTS_SOLID)),
mins = Vector(-8,-8,-8),
maxs = Vector(8,8,8)
}
local trace = util.TraceHull( tr )
if trace.Hit then
curpos = trace.HitPos
end
end
local trace = util.TraceLine({start=curpos,endpos=curpos+curang:Forward()*999999999,filter=self.Entities})
trace.RealStartPos = curpos
local hitPos = trace.HitPos or Vector(0,0,0)
if self.OldDupe then
WireLib.TriggerOutput(self, "X", hitPos.x)
WireLib.TriggerOutput(self, "Y", hitPos.y)
WireLib.TriggerOutput(self, "Z", hitPos.z)
end
WireLib.TriggerOutput(self,"HitPos",hitPos)
WireLib.TriggerOutput(self,"CamPos",curpos)
WireLib.TriggerOutput(self,"CamDir",curang:Forward())
WireLib.TriggerOutput(self,"CamAng",curang)
WireLib.TriggerOutput(self,"Trace",trace)
else
if self.OldDupe then
WireLib.TriggerOutput(self, "X", 0)
WireLib.TriggerOutput(self, "Y", 0)
WireLib.TriggerOutput(self, "Z", 0)
end
WireLib.TriggerOutput(self,"HitPos", Vector(0,0,0))
WireLib.TriggerOutput(self,"CamPos",Vector(0,0,0))
WireLib.TriggerOutput(self,"CamDir",Vector(0,0,0))
WireLib.TriggerOutput(self,"CamAng",Angle(0,0,0))
WireLib.TriggerOutput(self,"Trace",nil)
end
end
--------------------------------------------------
-- Think
--------------------------------------------------
function ENT:Think()
BaseClass.Think(self)
if self.NeedsSyncSettings then
self.NeedsSyncSettings = nil
self:SyncSettings()
end
if self.NeedsSyncPositions then
self.NeedsSyncPositions = nil
self:SyncPositions()
end
self:GetContraption()
self:UpdateOutputs()
self:NextThink(CurTime())
return true
end
--------------------------------------------------
-- PVS Hook
--------------------------------------------------
hook.Add("SetupPlayerVisibility", "gmod_wire_cameracontroller", function(player)
local cam = player.CamController
if IsValid(cam) then
local pos = cam.Position
if IsValid( cam.Parent ) then pos = cam.Parent:LocalToWorld(pos) end
AddOriginToPVS(pos)
end
end)
--------------------------------------------------
-- OnRemove
--------------------------------------------------
function ENT:OnRemove()
if IsValid( self.Parent ) then
self.Parent:RemoveCallOnRemove( "wire_camera_controller_remove_parent" )
end
self:ClearEntities()
end
--------------------------------------------------
-- DisableCam
--------------------------------------------------
function ENT:DisableCam( ply )
if #self.Vehicles == 0 and not ply then -- if the cam controller isn't linked, it controls the owner's view
ply = self:GetPlayer()
end
self:SetFOV( ply, false )
self:SetFLIR( ply, false )
self:SyncSettings( ply, false )
if IsValid( ply ) then
for i=1,#self.Players do
if self.Players[i] == ply then
table.remove( self.Players, i )
break
end
end
ply.CamController = nil
else
self.Players = {}
end
if #self.Players == 0 then
WireLib.TriggerOutput(self, "On", 0)
self.Active = false
self:ColorByLinkStatus(self.LINK_STATUS_LINKED)
end
end
--------------------------------------------------
-- EnableCam
--------------------------------------------------
function ENT:EnableCam( ply )
-- if we're in the middle of being pasted, then there may be linked vehicles
-- that we don't know about yet so we just ignore the call. See wiremod/wire#1062
if self.DuplicationInProgress then return end
if #self.Vehicles == 0 and not ply then -- if the cam controller isn't linked, it controls the owner's view
ply = self:GetPlayer()
end
if IsValid( ply ) then
for i=1,#self.Players do
if self.Players[i] == ply then return end -- check if this player is already active
end
self.Players[#self.Players+1] = ply
ply.CamController = self
self:SetFOV( ply )
self:SetFLIR( ply )
WireLib.TriggerOutput(self, "On", 1)
self.Active = true
self:ColorByLinkStatus(self.LINK_STATUS_ACTIVE)
self:SyncSettings( ply )
else -- No player specified, activate cam for everyone not already active
local lookup = {}
for i=1,#self.Players do lookup[self.Players[i]] = true end
for i=#self.Vehicles,1,-1 do
local veh = self.Vehicles[i]
if IsValid( veh ) then
local ply = veh:GetDriver()
if IsValid( ply ) then
if not lookup[ply] then
self:EnableCam( ply )
end
end
else
self:UnlinkEnt( veh )
end
end
end
end
--------------------------------------------------
-- SetFOV
--------------------------------------------------
function ENT:SetFOV( ply, b )
if b == nil and self.FOV ~= nil then b = true end
if self.FOV == 0 then b = false end
if IsValid( ply ) then
if b then
if not ply.Wire_Cam_DefaultFOV then
ply.Wire_Cam_DefaultFOV = ply:GetFOV()
end
if ply:GetFOV() ~= self.FOV then
ply:SetFOV( self.FOV, 0 )
end
elseif ply.Wire_Cam_DefaultFOV then
if ply:GetFOV() ~= ply.Wire_Cam_DefaultFOV then
ply:SetFOV( ply.Wire_Cam_DefaultFOV, 0 )
end
ply.Wire_Cam_DefaultFOV = nil
end
else
for i=#self.Players,1,-1 do
local ply = self.Players[i]
if IsValid(ply) then
self:SetFOV( ply, b )
else
table.remove( self.Players, i )
end
end
end
end
--------------------------------------------------
-- SetFLIR
--------------------------------------------------
function ENT:SetFLIR( ply, b )
if b == nil then b = self.FLIR end
if IsValid( ply ) then
if b then
FLIR.start( ply )
else
FLIR.stop( ply )
end
else
for i=#self.Players,1,-1 do
local ply = self.Players[i]
if IsValid(ply) then
self:SetFLIR( ply, b )
else
table.remove( self.Players, i )
end
end
end
end
--------------------------------------------------
-- LocalizePositions
--------------------------------------------------
function ENT:LocalizePositions(b)
if self.ParentLocal then return end
-- Localize the position if we have a parent
if IsValid( self.Parent ) then
local parent = self.Parent
if b then
self.Position = parent:WorldToLocal( self.Position )
self.Angle = parent:WorldToLocalAngles( self.Angle )
else
self.Position = parent:LocalToWorld( self.Position )
self.Angle = parent:LocalToWorldAngles( self.Angle )
end
end
end
--------------------------------------------------
-- TriggerInput
--------------------------------------------------
function ENT:TriggerInput( name, value )
if name == "Activated" then
self.Activated = value ~= 0
if value ~= 0 then self:EnableCam() else self:DisableCam() end
self:UpdateOverlay()
elseif name == "Zoom" or name == "FOV" then
self.FOV = math.Clamp( value, 0, 90 )
if not self.Activated then return end
self:SetFOV()
elseif name == "FLIR" then
self.FLIR = value ~= 0
if not self.Activated then return end
self:SetFLIR()
else
self:LocalizePositions(false)
if name == "Parent" then
self.Parent = value
elseif name == "Position" then
self.Position = value
elseif name == "Distance" then
self.Distance = value
elseif name == "UnRoll" then
self.UnRoll = tobool(value)
elseif name == "Direction" then
self.Angle = value:Angle()
elseif name == "Angle" then
self.Angle = value
elseif name == "X" then
self.Position.x = value
elseif name == "Y" then
self.Position.y = value
elseif name == "Z" then
self.Position.z = value
elseif name == "Pitch" then
self.Angle.p = value
elseif name == "Yaw" then
self.Angle.y = value
elseif name == "Roll" then
self.Angle.r = value
end
self:LocalizePositions(true)
self.NeedsSyncPositions = true
end
end
--------------------------------------------------
-- HiSpeed Access
--------------------------------------------------
local hispeed_ports = {
-- camera settings
[1] = "Activated",
[2] = "Parent",
[3] = "Zoom",
[4] = "FOV",
[5] = "FLIR",
-- camera position
[6] = "X",
[7] = "Y",
[8] = "Z",
[9] = "Distance",
-- camera angle (direction omitted as angle is the same thing)
[10] = "Pitch",
[11] = "Yaw",
[12] = "Roll",
[13] = "UnRoll",
-- controller settings
[14] = "ParentLocal",
[15] = "AutoMove",
[16] = "FreeMove",
[17] = "LocalMove",
[18] = "AllowZoom",
[19] = "AutoUnclip",
[20] = "AutoUnclip_IgnoreWater",
[21] = "DrawPlayer",
[22] = "DrawParent"
}
function ENT:WriteCell(address, value)
if not hispeed_ports[address] then return false end
local key = hispeed_ports[address]
if address < 14 then
if address == 2 then value = Entity( value ) end -- special case: parent entity by entid
self:TriggerInput(key, value)
return true
else
value = tobool( value )
if self[key] ~= value then
self[key] = value
self.NeedsSyncSettings = true
return true
end
end
return false
end
--------------------------------------------------
-- Enter/exit vehicle hooks
--------------------------------------------------
hook.Add("PlayerEnteredVehicle", "gmod_wire_cameracontroller", function(player, vehicle)
if IsValid(vehicle.CamController) and vehicle.CamController.Activated then
vehicle.CamController:EnableCam( player )
end
end)
hook.Add("PlayerLeaveVehicle", "gmod_wire_cameracontroller", function(player, vehicle)
if IsValid(vehicle.CamController) and vehicle.CamController.Activated then
vehicle.CamController:DisableCam( player )
end
end)
--------------------------------------------------
-- Leave camera manually
--------------------------------------------------
concommand.Add( "wire_cameracontroller_leave", function(player)
if IsValid(player.CamController) then
player.CamController:DisableCam( player )
end
end)
--------------------------------------------------
-- Linking to vehicles
--------------------------------------------------
function ENT:UpdateMarks()
WireLib.SendMarks(self,self.Vehicles)
end
function ENT:ClearEntities()
self:DisableCam()
for i=1,#self.Vehicles do
self.Vehicles[i]:RemoveCallOnRemove( "wire_camera_controller_remove_pod" )
self.Vehicles[i].CamController = nil
end
self.Vehicles = {}
self:UpdateMarks()
return true
end
function ENT:LinkEnt(pod)
pod = WireLib.GetClosestRealVehicle(pod,self:GetPos(),self:GetPlayer())
if not IsValid(pod) or not pod:IsVehicle() then return false, "Must link to a vehicle" end
for i=1,#self.Vehicles do
if self.Vehicles[i] == pod then
return false
end
end
pod.CamController = self
pod:CallOnRemove( "wire_camera_controller_remove_pod", function()
self:UnlinkEnt( pod )
end)
self.Vehicles[#self.Vehicles+1] = pod
self.Players = {}
if not self.Active then
if #self.Vehicles > 0 then
self:ColorByLinkStatus(self.LINK_STATUS_LINKED)
else
self:ColorByLinkStatus(self.LINK_STATUS_UNLINKED)
end
end
if IsValid( pod:GetDriver() ) then
self:EnableCam( pod:GetDriver() )
end
self:UpdateMarks()
return true
end
function ENT:UnlinkEnt(pod)
local idx = 0
for i=1,#self.Vehicles do
if self.Vehicles[i] == pod then
idx = i
break
end
end
if idx == 0 then return false end
if IsValid( pod:GetDriver() ) then
self:DisableCam( pod:GetDriver() )
end
pod:RemoveCallOnRemove( "wire_camera_controller_remove_pod" )
table.remove( self.Vehicles, idx )
pod.CamController = nil
if not self.Active then
if #self.Vehicles > 0 then
self:ColorByLinkStatus(self.LINK_STATUS_LINKED)
else
self:ColorByLinkStatus(self.LINK_STATUS_UNLINKED)
end
end
self:UpdateMarks()
return true
end
--------------------------------------------------
-- Dupe support
--------------------------------------------------
function ENT:BuildDupeInfo()
local info = BaseClass.BuildDupeInfo(self)
local veh = {}
for i=1,#self.Vehicles do
veh[i] = self.Vehicles[i]:EntIndex()
end
info.Vehicles = veh
info.OldDupe = self.OldDupe
-- Other options are saved using duplicator.RegisterEntityClass
return info
end
function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID)
BaseClass.ApplyDupeInfo(self, ply, ent, info, GetEntByID)
if info.cam or info.pod or info.OldDupe then -- OLD DUPE DETECTED
if info.cam then
local CamEnt = GetEntByID( info.cam )
if IsValid( CamEnt ) then CamEnt:Remove() end
end
if info.pod then
self.Vehicles[1] = GetEntByID( info.pod )
end
WireLib.AdjustSpecialInputs( self, { "Activated", "X", "Y", "Z", "Pitch", "Yaw", "Roll",
"Angle [ANGLE]", "Position [VECTOR]", "Distance", "UnRoll", "Direction [VECTOR]",
"Parent [ENTITY]", "FLIR", "FOV" } )
WireLib.AdjustSpecialOutputs( self, { "On", "X", "Y", "Z", "HitPos [VECTOR]",
"CamPos [VECTOR]", "CamDir [VECTOR]", "CamAng [ANGLE]",
"Trace [RANGER]" } )
self.OldDupe = true
else
local veh = info.Vehicles
if veh then
for i=1,#veh do
self:LinkEnt( GetEntByID( veh[i] ) )
end
end
end
timer.Simple( 0.1, function() if IsValid( self ) then self:UpdateMarks() end end ) -- timers solve everything (the entity isn't valid on the client at first, so we wait a bit)
end
WireLib.AddInputAlias( "Zoom", "FOV" )
WireLib.AddOutputAlias( "XYZ", "HitPos" )
duplicator.RegisterEntityClass("gmod_wire_cameracontroller", WireLib.MakeWireEnt, "Data", "ParentLocal","AutoMove","FreeMove","LocalMove","AllowZoom","AutoUnclip","DrawPlayer","AutoUnclip_IgnoreWater","DrawParent")