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")