--by pavetr.top/ds -- Settings CreateClientConVar("secondperson_maxdist", "2000", true, false, "Max distance to find nearest player") CreateClientConVar("secondperson_lookatplayer", "1", true, false, "Whether the camera looks at the player or uses player view") local enableSecondPerson = false local showSecondPersonHUD = true local lastNPC = nil local lastHeadBone = nil -- Toggle second person mode concommand.Add("secondperson_toggle", function() enableSecondPerson = not enableSecondPerson end) -- Find closest valid NPC local function GetClosestNPC() local ply = LocalPlayer() if not IsValid(ply) then return nil end local maxDistance = GetConVar("secondperson_maxdist"):GetFloat() local closestNPC local closestDist = maxDistance for _, ent in ipairs(player.GetAll()) do if IsValid(ent) and ent:Health() > 0 and ent!=LocalPlayer() then local dist = ply:GetPos():Distance(ent:GetPos()) if dist < closestDist then closestNPC = ent closestDist = dist end end end return closestNPC end local function GetNPCHeadPosAndAng(npc, targetPos) local lookAtPlayer = GetConVar("secondperson_lookatplayer"):GetBool() -- Try attachments local attachID = npc:LookupAttachment("eyes") or npc:LookupAttachment("head") if attachID and attachID > 0 then local attach = npc:GetAttachment(attachID) if attach then local angle = lookAtPlayer and (targetPos - attach.Pos):Angle() or attach.Ang return attach.Pos, angle end end -- Try head bone for i = 0, npc:GetBoneCount() - 1 do local boneName = npc:GetBoneName(i):lower() if boneName:find("head") then local bonePos, boneAng = npc:GetBonePosition(i) if bonePos then local angle = lookAtPlayer and (targetPos - bonePos):Angle() or boneAng return bonePos, angle end end end -- Fallback local fallbackPos = npc:GetPos() + Vector(0, 0, 64) local fallbackAng = lookAtPlayer and (targetPos - fallbackPos):Angle() or npc:GetAngles() return fallbackPos, fallbackAng end -- Camera override hook.Add("CalcView", "SecondPersonView", function(ply, pos, angles, fov) if not enableSecondPerson then return end local npc = GetClosestNPC() if IsValid(npc) then local headPos, viewAng = GetNPCHeadPosAndAng(npc, ply:GetPos() + Vector(0, 0, 64)) return { origin = headPos, angles = viewAng, fov = fov } end end) if exechack_api then exechack_api.CalcViewOverlayHook = function( ply, pos, angles, fov ) if enableSecondPerson then local npc = GetClosestNPC() if IsValid(npc) then local headPos, viewAng = GetNPCHeadPosAndAng(npc, ply:GetPos() + Vector(0, 0, 64)) local view = { origin = headPos, angles = viewAng, fov = fov } return view end end end end -- Show local player model hook.Add("ShouldDrawLocalPlayer", "SecondPersonDrawModel", function() if enableSecondPerson then return true end end) -- HUD: Display NPC being spectated hook.Add("HUDPaint", "SecondPersonNPCHud", function() if not enableSecondPerson then return end local npc = GetClosestNPC() if not IsValid(npc) then return end local text = "Viewing from: " .. npc:Name() surface.SetFont("Trebuchet24") local textW, textH = surface.GetTextSize(text) local x = ScrW() / 2 local y = ScrH() - 100 draw.SimpleText(text, "Trebuchet24", x + 2, y + 2, Color(0, 0, 0, 180), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) draw.SimpleText(text, "Trebuchet24", x, y, Color(255, 255, 255, 240), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) end) -- Scale NPC head bone to hide it local function SetHeadBoneScale(npc, boneName, scale) if not IsValid(npc) then return end local boneID = npc:LookupBone(boneName) if boneID then npc:ManipulateBoneScale(boneID, Vector(scale, scale, scale)) return boneID end end -- Think: Handle hiding/restoring NPC head bone hook.Add("Think", "SecondPersonBoneHide", function() if not enableSecondPerson then -- Restore previous NPC's head if IsValid(lastNPC) and lastHeadBone then lastNPC:ManipulateBoneScale(lastHeadBone, Vector(1, 1, 1)) end lastNPC = nil lastHeadBone = nil return end local npc = GetClosestNPC() if not IsValid(npc) then return end -- New NPC detected if npc ~= lastNPC then -- Restore old one if IsValid(lastNPC) and lastHeadBone then lastNPC:ManipulateBoneScale(lastHeadBone, Vector(1, 1, 1)) end -- Find and hide new NPC's head for i = 0, npc:GetBoneCount() - 1 do local boneName = npc:GetBoneName(i):lower() if boneName:find("head") then local boneID = SetHeadBoneScale(npc, boneName, 0) lastNPC = npc lastHeadBone = boneID break end end end end)