├── .gitignore ├── Makefile ├── README.md ├── addon.json ├── icon.xcf └── lua └── autorun ├── client └── enhanced_camera.lua └── server └── enhanced_camera.lua /.gitignore: -------------------------------------------------------------------------------- 1 | *.gma 2 | *.jpg 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GMOD_PATH = $(HOME)/.steam/steam/steamapps/common/GarrysMod 2 | GMAD = $(GMOD_PATH)/bin/gmad_linux 3 | GMPUBLISH = env LD_LIBRARY_PATH=$(GMOD_PATH)/bin $(GMOD_PATH)/bin/gmpublish_linux 4 | 5 | GMA_SRC = $(shell find lua -type f -name '*.lua') 6 | ICON_SRC = icon.xcf 7 | TXT_SRC = README.md 8 | 9 | TARGET = enhanced_camera 10 | WORKSHOP_ID = 678037029 11 | 12 | all: $(TARGET).gma $(TARGET).jpg 13 | 14 | $(TARGET).gma: $(GMA_SRC) 15 | $(GMAD) create -folder . -out $@ 16 | 17 | $(TARGET).jpg: $(ICON_SRC) 18 | convert $< -layers flatten $@ 19 | 20 | publish: $(TARGET).gma $(TARGET).jpg 21 | ifeq ($(WORKSHOP_ID),) 22 | $(GMPUBLISH) create -addon $(TARGET).gma -icon $(TARGET).jpg 23 | else 24 | $(GMPUBLISH) update -id $(WORKSHOP_ID) -addon $(TARGET).gma -icon $(TARGET).jpg -changes "$(shell git log -1 --pretty=%B)" 25 | endif 26 | 27 | clean: 28 | rm -f $(TARGET).gma $(TARGET).jpg 29 | 30 | .PHONY: all publish clean 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Garry's Mod: Enhanced Camera 2 | ============================ 3 | 4 | * [Steam Workshop](https://steamcommunity.com/sharedfiles/filedetails/?id=678037029) 5 | * [GitHub](https://github.com/mathewv/gmod-enhanced-camera) 6 | 7 | What is it? 8 | ----------- 9 | 10 | Enhanced Camera, based off the Oblivion/Skyrim/Fallout mods of the same name, is an addon for Garry's Mod that allows players to see their own bodies. The source code was originally based on [Gmod Legs 3](https://steamcommunity.com/sharedfiles/filedetails/?id=112806637), but has been modified so extensively and the two now have little in common besides a similar goal. It can be considered in a beta state. 11 | 12 | Why not Gmod Legs/Immersive First-Person? 13 | ----------------------------------------- 14 | 15 | Gmod Legs, as the title suggests, only shows the player's legs. It also requires a server-side component to work properly. [Immersive First-Person](https://steamcommunity.com/sharedfiles/filedetails/?id=133042891) is entirely client-side, but doesn't use viewmodels, modifies the camera origin (and thus breaks aiming), and either requires a strict camera pitch restriction or suffers a lot of clipping. Enhanced Camera, like the Oblivion/Skyrim/Fallout mods of the same name, combines the viewmodel and worldmodel and does not modify the camera origin. 16 | 17 | What this addon can and cannot do 18 | --------------------------------- 19 | 20 | Enhanced Camera *can*: 21 | 22 | * Work on the client-side, even on servers without the addon installed, as long as `sv_allowcslua` is enabled 23 | * Dynamically change the player's height to match their model if the serverside component is installed 24 | 25 | Enhanced Camera *can not*: 26 | 27 | * Work 100% of the time if models have broken paths unless the optional serverside component is installed, though it works most of the time 28 | * Show your PAC3 customizations (yet!) 29 | * Show your shadow (you can use `cl_drawownshadow` if you want, but it won't match your first person body or show your weapon's shadow) 30 | 31 | Console Commands and cvars 32 | -------------------------- 33 | 34 | **Client-side**: All of these options can be configured in the Tools menu, Options tab. 35 | 36 | * `cl_ec_enabled` 37 | * `1` (Default): Show your body in first-person 38 | * `0`: Hide your body in first-person 39 | * `cl_ec_showhair` 40 | * `1` (Default): Show your hair (bones attached to head) in first-person 41 | * `0`: Hide your hair in first-person 42 | * `cl_ec_vehicle` 43 | * `1` (Default): Show your body while in vehicles 44 | * `0`: Hide your body while in vehicles 45 | * `cl_ec_vehicle_yawlock` 46 | * `1` (Default): Restrict yaw while in vehicles to prevent looking backwards at your neck. Yaw is not restricted regardless of this setting if either `cl_ec_enabled` or `cl_ec_vehicle` is `0`. 47 | * `0`: Unrestrict yaw while in vehicles 48 | * `cl_ec_vehicle_yawlock_max` 49 | * (Default: `65`): Angle (in degrees) you can look away from the center view of a vehicle when `cl_ec_vehicle_yawlock` is enabled. 50 | * `cl_ec_refresh` 51 | * Forces a model reload. May be useful if the first-person model doesn't update after changing your playermodel for some reason. 52 | * `cl_ec_toggle` 53 | * Toggles the visibility of your body in first-person 54 | * `cl_ec_togglevehicle` 55 | * Toggles the visibility of your body in first-person while in vehicles 56 | 57 | **Server-side** 58 | 59 | * `sv_ec_dynamicheight` 60 | * `1` (Default): Dynamically adjust players' view heights to match their models 61 | * `0`: Don't touch players' heights 62 | * `sv_ec_dynamicheight_min` 63 | * (Default: `16`): Minimum view height 64 | * `sv_ec_dynamicheight_max` 65 | * (Default: `64`): Maximum view height 66 | -------------------------------------------------------------------------------- /addon.json: -------------------------------------------------------------------------------- 1 | { 2 | "title" : "Enhanced Camera", 3 | "type" : "effects", 4 | "tags" : [ "roleplay", "realism" ], 5 | "ignore": [ 6 | ".git/*", 7 | ".gitignore", 8 | "icon.xcf", 9 | "Makefile", 10 | "README.md", 11 | "enhanced_camera.gma", 12 | "enhanced_camera.jpg" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /icon.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elizagamedev/gmod-enhanced-camera/00ee1ef84be0ebc23a811c10f81df1a127385e39/icon.xcf -------------------------------------------------------------------------------- /lua/autorun/client/enhanced_camera.lua: -------------------------------------------------------------------------------- 1 | local cvarEnabled = CreateClientConVar("cl_ec_enabled", "1") 2 | local cvarHair = CreateClientConVar("cl_ec_showhair", "1") 3 | local cvarVehicle = CreateClientConVar("cl_ec_vehicle", "1") 4 | local cvarVehicleYawLock = CreateClientConVar("cl_ec_vehicle_yawlock", "1") 5 | local cvarVehicleYawLockMax = CreateClientConVar("cl_ec_vehicle_yawlock_max", "65") 6 | 7 | EnhancedCamera = EnhancedCamera or { 8 | -- Animation/Rendering 9 | entity = nil, 10 | skelEntity = nil, 11 | lastTick = 0, 12 | 13 | -- Variables to detect change in model state 14 | model = nil, 15 | bodyGroups = nil, 16 | materials = nil, 17 | skin = nil, 18 | material = nil, 19 | color = nil, 20 | 21 | -- Variables to detect change in pose state 22 | weapon = nil, 23 | sequence = nil, 24 | reloading = false, 25 | 26 | -- Pose-dependent variables 27 | pose = "", 28 | viewOffset = Vector(0, 0, 0), 29 | neckOffset = Vector(0, 0, 0), 30 | vehicleAngle = 0, 31 | 32 | -- Model-dependent variables 33 | ragdollSequence = nil, 34 | idleSequence = nil, 35 | 36 | -- API variables 37 | apiBoneHide = {} 38 | } 39 | 40 | -- PUBLIC API 41 | function EnhancedCamera:SetLimbHidden(limb, hidden) 42 | -- `limb` may be "l_arm", "r_arm", "l_leg", or "r_leg" 43 | -- `hidden` is a bool describing the desired visibility 44 | -- Limbs hidden because of pose will not be visible regardless of this setting. 45 | self.apiBoneHide[limb] = hidden and true or nil 46 | self:Refresh() 47 | end 48 | 49 | -- Global functions 50 | local function ApproximatePlayerModel() 51 | -- Return a value suitable for detecting model changes 52 | return LocalPlayer():GetNWString("EnhancedCamera:TrueModel", LocalPlayer():GetModel()) 53 | end 54 | 55 | local function GetPlayerBodyGroups() 56 | local bodygroups = {} 57 | for k, v in pairs(LocalPlayer():GetBodyGroups()) do 58 | bodygroups[v.id] = LocalPlayer():GetBodygroup(v.id) 59 | end 60 | return bodygroups 61 | end 62 | 63 | local function GetPlayerMaterials() 64 | local materials = {} 65 | for k, v in pairs(LocalPlayer():GetMaterials()) do 66 | materials[k - 1] = LocalPlayer():GetSubMaterial(k - 1) 67 | end 68 | return materials 69 | end 70 | 71 | -- Body entity functions 72 | function EnhancedCamera:SetModel(model) 73 | if not IsValid(self.entity) then 74 | self.entity = ClientsideModel(model) 75 | self.entity:SetNoDraw(true) 76 | self.entity.GetPlayerColor = function() 77 | return LocalPlayer():GetPlayerColor() 78 | end 79 | self.entity.GetWeaponColor = function() 80 | return LocalPlayer():GetWeaponColor() 81 | end 82 | else 83 | self.entity:SetModel(model) 84 | end 85 | if not IsValid(self.skelEntity) then 86 | self.skelEntity = ClientsideModel(model) 87 | self.skelEntity:SetNoDraw(true) 88 | else 89 | self.skelEntity:SetModel(model) 90 | end 91 | 92 | self.ragdollSequence = self.entity:LookupSequence("ragdoll") 93 | self.idleSequence = self.entity:LookupSequence("idle_all_01") 94 | end 95 | 96 | function EnhancedCamera:ResetSequence(seq) 97 | self.entity:ResetSequence(seq) 98 | self.skelEntity:ResetSequence(seq) 99 | end 100 | 101 | function EnhancedCamera:SetPlaybackRate(fSpeed) 102 | self.entity:SetPlaybackRate(fSpeed) 103 | self.skelEntity:SetPlaybackRate(fSpeed) 104 | end 105 | 106 | function EnhancedCamera:FrameAdvance(delta) 107 | self.entity:FrameAdvance(delta) 108 | self.skelEntity:FrameAdvance(delta) 109 | end 110 | 111 | function EnhancedCamera:SetPoseParameter(poseName, poseValue) 112 | self.entity:SetPoseParameter(poseName, poseValue) 113 | self.skelEntity:SetPoseParameter(poseName, poseValue) 114 | end 115 | 116 | -- Body utility functions 117 | function EnhancedCamera:HasChanged(key, newvalue) 118 | if self[key] ~= newvalue then 119 | self[key] = newvalue 120 | return true 121 | end 122 | return false 123 | end 124 | 125 | function EnhancedCamera:HasTableChanged(key, newtable) 126 | local tbl = self[key] 127 | if tbl == newtable then 128 | return false 129 | end 130 | if tbl == nil or newtable == nil then 131 | self[key] = newtable 132 | return true 133 | end 134 | if table.getn(tbl) ~= table.getn(newtable) then 135 | self[key] = newtable 136 | return true 137 | end 138 | for k, v in pairs(tbl) do 139 | if newtable[k] ~= v then 140 | self[key] = newtable 141 | return true 142 | end 143 | end 144 | return false 145 | end 146 | 147 | function EnhancedCamera:Refresh() 148 | self.model = nil 149 | self.sequence = nil 150 | self.pose = nil 151 | end 152 | 153 | -- Body state functions 154 | function EnhancedCamera:ShouldDraw() 155 | return cvarEnabled:GetBool() and 156 | (not LocalPlayer():InVehicle() or cvarVehicle:GetBool()) and 157 | IsValid(self.entity) and 158 | IsValid(self.skelEntity) and 159 | LocalPlayer():Alive() and 160 | GetViewEntity() == LocalPlayer() and 161 | not LocalPlayer():ShouldDrawLocalPlayer() and 162 | LocalPlayer():GetObserverMode() == 0 163 | end 164 | 165 | function EnhancedCamera:GetPose() 166 | -- Weapon:Getpose() is very unreliable at the time of writing. 167 | local seqname = LocalPlayer():GetSequenceName(self.sequence) 168 | local wep = LocalPlayer():GetActiveWeapon() 169 | if seqname == "ragdoll" then 170 | return "normal" 171 | elseif string.StartWith(seqname, "sit") then 172 | return "sit" 173 | elseif seqname == "drive_pd" then 174 | return "pod" 175 | elseif string.StartWith(seqname, "drive") then 176 | return "drive" 177 | end 178 | local pose = string.sub(seqname, string.find(seqname, "_") + 1) 179 | pose = (wep and wep.DefaultHoldType) or pose 180 | if string.find(pose, "all") then 181 | return "normal" 182 | elseif pose == "smg1" then 183 | return "smg" 184 | end 185 | return pose 186 | end 187 | 188 | function EnhancedCamera:GetModel() 189 | -- Try to find the actual player model based on the often vague guess given 190 | -- by GetModel() 191 | name = self.model 192 | if util.IsValidModel(name) then return name end 193 | 194 | -- Search for a matching model name in the list of valid models 195 | local basename = string.GetFileFromFilename(name) 196 | for _, name in pairs(player_manager.AllValidModels()) do 197 | if string.GetFileFromFilename(name) == basename then 198 | return name 199 | end 200 | end 201 | 202 | return "models/player/kleiner.mdl" 203 | end 204 | 205 | function EnhancedCamera:GetSequence() 206 | local sequence = LocalPlayer():GetSequence() 207 | if sequence == self.ragdollSequence then 208 | return self.idleSequence 209 | end 210 | return sequence 211 | end 212 | 213 | function EnhancedCamera:GetRenderPosAngle() 214 | local renderPos = EyePos() 215 | local renderAngle = nil 216 | 217 | if LocalPlayer():InVehicle() then 218 | renderAngle = LocalPlayer():GetVehicle():GetAngles() 219 | renderAngle:RotateAroundAxis(renderAngle:Up(), self.vehicleAngle) 220 | else 221 | renderAngle = Angle(0, LocalPlayer():EyeAngles().y, 0) 222 | end 223 | 224 | local offset = self.viewOffset - self.neckOffset 225 | offset:Rotate(renderAngle) 226 | -- Adjust offset for crouching 227 | if LocalPlayer():GetGroundEntity() ~= NULL and LocalPlayer():Crouching() then 228 | offset.z = offset.z + 21 229 | end 230 | renderPos = renderPos + offset 231 | return renderPos, renderAngle 232 | end 233 | 234 | -- Set up the body model to match the player model 235 | function EnhancedCamera:OnModelChange() 236 | self:SetModel(self:GetModel()) 237 | 238 | for k, v in pairs(self.bodyGroups) do 239 | self.entity:SetBodygroup(k, v) 240 | end 241 | 242 | if self:HasTableChanged('materials', GetPlayerMaterials()) then 243 | for k, v in pairs(self.materials) do 244 | self.entity:SetSubMaterial(k, v) 245 | end 246 | end 247 | 248 | self.entity:SetSkin(self.skin) 249 | self.entity:SetMaterial(self.material) 250 | self.entity:SetColor(self.color) 251 | 252 | -- Update new pose 253 | self.lastTick = 0 254 | self.sequence = nil 255 | end 256 | 257 | local POSE_SHOW_ARM = { 258 | left = { 259 | normal = true, 260 | sit = true, 261 | drive = true, 262 | pod = true, 263 | }, 264 | right = { 265 | normal = true, 266 | sit = true, 267 | drive = true, 268 | pod = true, 269 | }, 270 | } 271 | 272 | local NAME_SHOW_ARM = { 273 | left = { 274 | weapon_crowbar = true, 275 | weapon_pistol = true, 276 | weapon_stunstick = true, 277 | gmod_tool = true, 278 | }, 279 | right = { 280 | }, 281 | } 282 | 283 | local NAME_HIDE_ARM = { 284 | left = { 285 | }, 286 | right = { 287 | weapon_bugbait = true, 288 | }, 289 | } 290 | 291 | -- Hide limbs as appropriate for the current hold type and record the hold 292 | -- type for use elsewhere 293 | function EnhancedCamera:OnPoseChange() 294 | for i = 0, self.entity:GetBoneCount() do 295 | self.entity:ManipulateBoneScale(i, Vector(1, 1, 1)) 296 | self.entity:ManipulateBonePosition(i, vector_origin) 297 | end 298 | 299 | -- Hide appropriate limbs 300 | local wep = LocalPlayer():GetActiveWeapon() 301 | local name = IsValid(wep) and wep:GetClass() or "" 302 | local bone = self.entity:LookupBone("ValveBiped.Bip01_Head1") 303 | self.entity:ManipulateBoneScale(bone, vector_origin) 304 | if not cvarHair:GetBool() then 305 | self.entity:ManipulateBonePosition(bone, Vector(-128, 128, 0)) 306 | end 307 | if self.apiBoneHide['l_arm'] or self.reloading or not ( 308 | (POSE_SHOW_ARM.left[self.pose] or 309 | NAME_SHOW_ARM.left[name]) and not 310 | NAME_HIDE_ARM.left[name]) then 311 | bone = self.entity:LookupBone("ValveBiped.Bip01_L_Upperarm") 312 | self.entity:ManipulateBoneScale(bone, vector_origin) 313 | self.entity:ManipulateBonePosition(bone, Vector(0, 0, -128)) 314 | end 315 | if self.apiBoneHide['r_arm'] or self.reloading or not ( 316 | (POSE_SHOW_ARM.right[self.pose] or 317 | NAME_SHOW_ARM.right[name]) and not 318 | NAME_HIDE_ARM.right[name]) then 319 | bone = self.entity:LookupBone("ValveBiped.Bip01_R_Upperarm") 320 | self.entity:ManipulateBoneScale(bone, vector_origin) 321 | self.entity:ManipulateBonePosition(bone, Vector(0, 0, 128)) 322 | end 323 | if self.apiBoneHide['l_leg'] then 324 | bone = self.entity:LookupBone("ValveBiped.Bip01_L_Thigh") 325 | self.entity:ManipulateBoneScale(bone, vector_origin) 326 | self.entity:ManipulateBonePosition(bone, Vector(0, 0, -128)) 327 | end 328 | if self.apiBoneHide['r_leg'] then 329 | bone = self.entity:LookupBone("ValveBiped.Bip01_R_Thigh") 330 | self.entity:ManipulateBoneScale(bone, vector_origin) 331 | self.entity:ManipulateBonePosition(bone, Vector(0, 0, -128)) 332 | end 333 | 334 | -- Set pose-specific view offset 335 | if self.pose == "normal" or self.pose == "camera" or self.pose == "fist" or 336 | self.pose == "dual" or self.pose == "passive" or self.pose == "magic" then 337 | self.viewOffset = Vector(-10, 0, -5) 338 | elseif self.pose == "melee" or self.pose == "melee2" or 339 | self.pose == "grenade" or self.pose == "slam" then 340 | self.viewOffset = Vector(-10, 0, -5) 341 | elseif self.pose == "knife" then 342 | self.viewOffset = Vector(-6, 0, -5) 343 | elseif self.pose == "pistol" or self.pose == "revolver" then 344 | self.viewOffset = Vector(-10, 0, -5) 345 | elseif self.pose == "smg" or self.pose == "ar2" or self.pose == "rpg" or 346 | self.pose == "shotgun" or self.pose == "crossbow" or self.pose == "physgun" then 347 | self.viewOffset = Vector(-10, 4, -5) 348 | elseif self.pose == "sit" then 349 | self.viewOffset = Vector(-6, 0, 0) 350 | elseif self.pose == "drive" then 351 | self.viewOffset = Vector(-2, 0, -4) 352 | elseif self.pose == "pod" then 353 | self.viewOffset = Vector(-8, 0, -4) 354 | else 355 | self.viewOffset = Vector(0, 0, 0) 356 | end 357 | 358 | -- Set vehicle view angle 359 | self.vehicleAngle = (self.pose == "pod") and 0 or 90 360 | end 361 | 362 | function EnhancedCamera:Think(maxSeqGroundSpeed) 363 | local modelChanged = false 364 | local poseChanged = false 365 | 366 | -- Handle model changes 367 | modelChanged = self:HasChanged('model', ApproximatePlayerModel()) or modelChanged 368 | modelChanged = self:HasTableChanged('bodyGroups', GetPlayerBodyGroups()) or modelChanged 369 | --modelChanged = self:HasTableChanged('materials', GetPlayerMaterials()) or modelChanged 370 | modelChanged = self:HasChanged('skin', LocalPlayer():GetSkin()) or modelChanged 371 | modelChanged = self:HasChanged('material', LocalPlayer():GetMaterial()) or modelChanged 372 | modelChanged = self:HasTableChanged('color', LocalPlayer():GetColor()) or modelChanged 373 | if not IsValid(self.entity) or modelChanged then 374 | poseChanged = true 375 | self:OnModelChange() 376 | end 377 | 378 | -- Set flexes to match 379 | -- Flexes will reset if not set on every frame 380 | for i = 0, LocalPlayer():GetFlexNum()-1 do 381 | self.entity:SetFlexWeight(i, LocalPlayer():GetFlexWeight(i) ) 382 | end 383 | 384 | -- Test if sequence changed 385 | if self:HasChanged('sequence', self:GetSequence()) then 386 | self:ResetSequence(self.sequence) 387 | if self:HasChanged('pose', self:GetPose()) then 388 | poseChanged = true 389 | end 390 | end 391 | 392 | -- Test if weapon changed 393 | if self:HasChanged('weapon', LocalPlayer():GetActiveWeapon()) then 394 | self.reloading = false 395 | poseChanged = true 396 | end 397 | 398 | -- Test if reload is finished 399 | if self.reloading then 400 | if IsValid(self.weapon) then 401 | local time = CurTime() 402 | if self.weapon:GetNextPrimaryFire() < time and self.weapon:GetNextSecondaryFire() < time then 403 | self.reloading = false 404 | poseChanged = true 405 | end 406 | else 407 | self.reloading = false 408 | end 409 | end 410 | 411 | -- Handle weapon changes 412 | if poseChanged then self:OnPoseChange() end 413 | 414 | -- Update the animation playback rate 415 | local velocity = LocalPlayer():GetVelocity():Length2D() 416 | 417 | local playbackRate = 1 418 | 419 | if velocity > 0.5 then 420 | if maxSeqGroundSpeed < 0.001 then 421 | playbackRate = 0.01 422 | else 423 | playbackRate = velocity / maxSeqGroundSpeed 424 | playbackRate = math.Clamp(playbackRate, 0.01, 10) 425 | end 426 | end 427 | 428 | self:SetPlaybackRate(playbackRate) 429 | 430 | self:FrameAdvance(CurTime() - self.lastTick) 431 | self.lastTick = CurTime() 432 | 433 | -- Pose remainder of model 434 | self:SetPoseParameter("breathing", LocalPlayer():GetPoseParameter("breathing")) 435 | self:SetPoseParameter("move_x", (LocalPlayer():GetPoseParameter("move_x") * 2) - 1) 436 | self:SetPoseParameter("move_y", (LocalPlayer():GetPoseParameter("move_y") * 2) - 1) 437 | self:SetPoseParameter("move_yaw", (LocalPlayer():GetPoseParameter("move_yaw") * 360) - 180) 438 | 439 | -- Pose vehicle steering 440 | if LocalPlayer():InVehicle() then 441 | self.entity:SetColor(color_transparent) 442 | self:SetPoseParameter("vehicle_steer", (LocalPlayer():GetVehicle():GetPoseParameter("vehicle_steer") * 2) - 1) 443 | end 444 | 445 | -- Update skeleton neck offset 446 | self.neckOffset = self.skelEntity:GetBonePosition(self.skelEntity:LookupBone("ValveBiped.Bip01_Neck1")) 447 | end 448 | 449 | hook.Add("UpdateAnimation", "EnhancedCamera:UpdateAnimation", function(ply, velocity, maxSeqGroundSpeed) 450 | if ply == LocalPlayer() then 451 | EnhancedCamera:Think(maxSeqGroundSpeed) 452 | end 453 | end) 454 | 455 | -- On start of reload animation 456 | hook.Add("DoAnimationEvent", "EnhancedCamera:DoAnimationEvent", function(ply, event, data) 457 | if ply == LocalPlayer() and event == PLAYERANIMEVENT_RELOAD then 458 | EnhancedCamera.reloading = true 459 | EnhancedCamera:OnPoseChange() 460 | end 461 | end) 462 | 463 | function EnhancedCamera:Render() 464 | if self:ShouldDraw() then 465 | local renderColor = LocalPlayer():GetColor() 466 | local renderPos, renderAngle = self:GetRenderPosAngle() 467 | 468 | cam.Start3D(EyePos(), EyeAngles()) 469 | render.SetColorModulation(renderColor.r / 255, renderColor.g / 255, renderColor.b / 255) 470 | render.SetBlend(renderColor.a / 255) 471 | self.entity:SetRenderOrigin(renderPos) 472 | self.entity:SetRenderAngles(renderAngle) 473 | self.entity:SetupBones() 474 | self.entity:DrawModel() 475 | self.entity:SetRenderOrigin() 476 | self.entity:SetRenderAngles() 477 | render.SetBlend(1) 478 | render.SetColorModulation(1, 1, 1) 479 | cam.End3D() 480 | end 481 | end 482 | 483 | hook.Add("PreDrawEffects", "EnhancedCamera:RenderScreenspaceEffects", function() 484 | EnhancedCamera:Render() 485 | end) 486 | 487 | -- Lock yaw in vehicles 488 | hook.Add("CreateMove", "EnhancedCamera:CreateMove", function(ucmd) 489 | if EnhancedCamera:ShouldDraw() and cvarVehicleYawLock:GetBool() and LocalPlayer():InVehicle() then 490 | ang = ucmd:GetViewAngles() 491 | max = cvarVehicleYawLockMax:GetInt() 492 | yaw = math.Clamp(math.NormalizeAngle(ang.y - EnhancedCamera.vehicleAngle), -max, max) + EnhancedCamera.vehicleAngle 493 | ucmd:SetViewAngles(Angle(ang.p, yaw, ang.r)) 494 | end 495 | end) 496 | 497 | -- Console commands 498 | concommand.Add("cl_ec_toggle", function() 499 | if cvarEnabled:GetBool() then 500 | RunConsoleCommand("cl_ec_enabled", "0") 501 | else 502 | RunConsoleCommand("cl_ec_enabled", "1") 503 | end 504 | end) 505 | 506 | concommand.Add("cl_ec_togglevehicle", function() 507 | if cvarVehicle:GetBool() then 508 | RunConsoleCommand("cl_ec_vehicle", "0") 509 | else 510 | RunConsoleCommand("cl_ec_vehicle", "1") 511 | end 512 | end) 513 | 514 | concommand.Add("cl_ec_refresh", function() 515 | EnhancedCamera:Refresh() 516 | end) 517 | 518 | cvars.AddChangeCallback("cl_ec_showhair", function(name, oldVal, newVal) 519 | EnhancedCamera:Refresh() 520 | end) 521 | 522 | -- Options Menu 523 | hook.Add("PopulateToolMenu", "EnhancedCamera:PopulateToolMenu", function() 524 | spawnmenu.AddToolMenuOption("Options", "Enhanced Camera", "EnhancedCamera", "Options", "", "", function(panel) 525 | panel:AddControl("CheckBox", { 526 | Label = "Show body", 527 | Command = "cl_ec_enabled", 528 | }) 529 | 530 | panel:AddControl("CheckBox", { 531 | Label = "Show hair", 532 | Command = "cl_ec_showhair", 533 | }) 534 | 535 | panel:AddControl("CheckBox", { 536 | Label = "Show body in vehicles", 537 | Command = "cl_ec_vehicle", 538 | }) 539 | 540 | panel:AddControl("CheckBox", { 541 | Label = "Restrict view in vehicles", 542 | Command = "cl_ec_vehicle_yawlock", 543 | }) 544 | 545 | panel:AddControl("Slider", { 546 | Label = "Vehicle view restrict amount", 547 | Command = "cl_ec_vehicle_yawlock_max", 548 | Min = 5, 549 | Max = 180, 550 | }) 551 | end) 552 | end) 553 | -------------------------------------------------------------------------------- /lua/autorun/server/enhanced_camera.lua: -------------------------------------------------------------------------------- 1 | AddCSLuaFile("../client/enhanced_camera.lua") 2 | 3 | local cvarHeightEnabled = CreateConVar("sv_ec_dynamicheight", "1", {FCVAR_SERVER_CAN_EXECUTE, FCVAR_ARCHIVE, FCVAR_REPLICATED}) 4 | local cvarHeightMin = CreateConVar("sv_ec_dynamicheight_min", "16", {FCVAR_SERVER_CAN_EXECUTE, FCVAR_ARCHIVE}) 5 | local cvarHeightMax = CreateConVar("sv_ec_dynamicheight_max", "64", {FCVAR_SERVER_CAN_EXECUTE, FCVAR_ARCHIVE}) 6 | 7 | local function UpdateView(ply) 8 | if cvarHeightEnabled:GetBool() then 9 | -- Find the height by spawning a dummy entity 10 | local height = 64 11 | local entity = ents.Create("base_anim") 12 | entity:SetModel(ply:GetModel()) 13 | entity:ResetSequence(entity:LookupSequence("idle_all_01")) 14 | local bone = entity:LookupBone("ValveBiped.Bip01_Neck1") 15 | if bone then 16 | height = entity:GetBonePosition(bone).z + 5 17 | end 18 | entity:Remove() 19 | 20 | -- Update player height 21 | local min = cvarHeightMin:GetInt() 22 | local max = cvarHeightMax:GetInt() 23 | ply:SetViewOffset(Vector(0, 0, math.Clamp(height, min, max))) 24 | ply:SetViewOffsetDucked(Vector(0, 0, math.Clamp(height - 36, min, max))) 25 | ply.ec_ViewChanged = true 26 | else 27 | if ply.ec_ViewChanged then 28 | ply:SetViewOffset(Vector(0, 0, 64)) 29 | ply:SetViewOffsetDucked(Vector(0, 0, 28)) 30 | ply.ec_ViewChanged = nil 31 | end 32 | end 33 | end 34 | 35 | local function UpdateTrueModel(ply) 36 | if ply:GetNWString("EnhancedCamera:TrueModel") ~= ply:GetModel() then 37 | ply:SetNWString("EnhancedCamera:TrueModel", ply:GetModel()) 38 | UpdateView(ply) 39 | end 40 | end 41 | 42 | hook.Add("PlayerSpawn", "EnhancedCamera:PlayerSpawn", function(ply) 43 | UpdateTrueModel(ply) 44 | end) 45 | 46 | hook.Add("PlayerTick", "EnhancedCamera:PlayerTick", function(ply) 47 | UpdateTrueModel(ply) 48 | end) 49 | 50 | local function ConVarChanged(name, oldVal, newVal) 51 | for _, ply in pairs(player.GetAll()) do 52 | UpdateView(ply) 53 | end 54 | end 55 | 56 | cvars.AddChangeCallback("sv_ec_dynamicheight", ConVarChanged) 57 | cvars.AddChangeCallback("sv_ec_dynamicheight_min", ConVarChanged) 58 | cvars.AddChangeCallback("sv_ec_dynamicheight_max", ConVarChanged) 59 | --------------------------------------------------------------------------------