local TAG = "SitAny_" local PLUGIN = PLUGIN SitAnywhere = SitAnywhere or {} local NextUse = setmetatable({}, {__mode = 'k', __index = function() return 0 end}) -- Конфигурация через ix.config ix.config.Add("sitOnPlayers", true, "Разрешить сидеть на игроках в SitAnywhere", nil, { category = "Sit Anywhere" }) ix.config.Add("sitOnPlayerEntities", true, "Разрешить сидеть на сущностях игроков", nil, { category = "Sit Anywhere" }) ix.config.Add("sitPlayerDamage", false, "Разрешить урон сидящим игрокам", nil, { category = "Sit Anywhere" }) ix.config.Add("sitAllowWeapons", false, "Разрешить использование оружия при сидении", nil, { category = "Sit Anywhere" }) ix.config.Add("sitAdminOnly", false, "Только админы могут сидеть", nil, { category = "Sit Anywhere" }) ix.config.Add("sitAntiPropSurf", true, "Запретить физган на объектах с сидящими игроками", nil, { category = "Sit Anywhere" }) ix.config.Add("sitAntiToolAbuse", true, "Запретить тулган на объектах с сидящими игроками", nil, { category = "Sit Anywhere" }) ix.config.Add("sitAllowTightPlaces", false, "Разрешить сидеть в тесных местах", nil, { category = "Sit Anywhere" }) ix.config.Add("sitForceNoWalk", false, "Отключить необходимость шага для сидения", nil, { category = "Sit Anywhere" }) local META = FindMetaTable("Player") util.AddNetworkString("SitAnywhere") net.Receive("SitAnywhere", function(len, ply) local netID = net.ReadInt(4) if netID == SitAnywhere.NET.SitWantedAng then local wantedAng, traceStart, traceNormal = net.ReadFloat(), net.ReadVector(), net.ReadVector() if traceStart:Distance(ply:EyePos()) > 10 then return end local trace = util.TraceLine({ start = traceStart, endpos = traceStart + traceNormal * 12000, filter = player.GetAll() }) ply:Sit(trace, nil, nil, nil, nil, nil, wantedAng) elseif netID == SitAnywhere.NET.SitRequestExit then ply:ExitSit() end end) local function Sit(ply, pos, ang, parent, parentbone, func, exit) if IsValid(ply:GetVehicle()) then local veh = ply:GetVehicle() if veh:GetClass() == "prop_vehicle_prisoner_pod" and IsValid(veh.holder) then SafeRemoveEntity(veh.holder) end ply:ExitVehicle() end local vehicle = ents.Create("prop_vehicle_prisoner_pod") local t = hook.Run("OnPlayerSit", ply, pos, ang, parent or NULL, parentbone, vehicle) if t == false then SafeRemoveEntity(vehicle) return false end vehicle:SetAngles(ang) pos = pos + vehicle:GetUp() * 18 vehicle:SetPos(pos) vehicle.playerdynseat = true vehicle:SetNWBool("playerdynseat", true) vehicle.sittingPly = ply vehicle.oldpos = vehicle:WorldToLocal(ply:GetPos()) vehicle.wasCrouching = ply:Crouching() vehicle:SetModel("models/nova/airboat_seat.mdl") vehicle:SetKeyValue("vehiclescript", "scripts/vehicles/prisoner_pod.txt") vehicle:SetKeyValue("limitview", "0") vehicle:Spawn() vehicle:Activate() if not IsValid(vehicle) or not IsValid(vehicle:GetPhysicsObject()) then SafeRemoveEntity(vehicle) return false end local phys = vehicle:GetPhysicsObject() vehicle:SetMoveType(MOVETYPE_PUSH) phys:Sleep() vehicle:SetCollisionGroup(COLLISION_GROUP_WORLD) vehicle:SetNotSolid(true) phys:Sleep() phys:EnableGravity(false) phys:EnableMotion(false) phys:EnableCollisions(false) phys:SetMass(1) vehicle:CollisionRulesChanged() vehicle:DrawShadow(false) vehicle:SetColor(Color(0,0,0,0)) vehicle:SetRenderMode(RENDERMODE_TRANSALPHA) vehicle:SetNoDraw(true) vehicle.VehicleName = "Airboat Seat" vehicle.ClassOverride = "prop_vehicle_prisoner_pod" vehicle.PhysgunDisabled = true vehicle.m_tblToolsAllowed = {} vehicle.customCheck = function() return false end if IsValid(parent) then local r = math.rad(ang.yaw + 90) vehicle.plyposhack = vehicle:WorldToLocal(pos + Vector(math.cos(r) * 2, math.sin(r) * 2, 2)) vehicle:SetParent(parent) vehicle.parent = parent else vehicle.OnWorld = true end local prev = ply:GetAllowWeaponsInVehicle() if prev then ply.sitting_allowswep = nil elseif ix.config.Get("sitAllowWeapons", false) then ply.sitting_allowswep = prev ply:SetAllowWeaponsInVehicle(true) end ply:EnterVehicle(vehicle) if ix.config.Get("sitPlayerDamage", false) then ply:SetCollisionGroup(COLLISION_GROUP_WEAPON) ply:CollisionRulesChanged() end if func then func(ply) end ply.seatExit = exit ply:SetEyeAngles(Angle(0,90,0)) return vehicle end local d = function(a,b) return math.abs(a-b) end local SittingOnPlayerPoses = { { Pos = Vector(-33, 13, 7), Ang = Angle(0, 90, 90), FindAng = 90, }, { Pos = Vector(33, 13, 7), Ang = Angle(0, 270, 90), Func = function(ply) if not ply:LookupBone("ValveBiped.Bip01_R_Thigh") then return end ply:ManipulateBoneAngles(ply:LookupBone("ValveBiped.Bip01_R_Thigh"), Angle(0,90,0)) ply:ManipulateBoneAngles(ply:LookupBone("ValveBiped.Bip01_L_Thigh"), Angle(0,90,0)) end, OnExitFunc = function(ply) if not ply:LookupBone("ValveBiped.Bip01_R_Thigh") then return end ply:ManipulateBoneAngles(ply:LookupBone("ValveBiped.Bip01_R_Thigh"), Angle(0,0,0)) ply:ManipulateBoneAngles(ply:LookupBone("ValveBiped.Bip01_L_Thigh"), Angle(0,0,0)) end, FindAng = 270, }, { Pos = Vector(0, 16, -15), Ang = Angle(0, 180, 0), Func = function(ply) if not ply:LookupBone("ValveBiped.Bip01_R_Thigh") then return end ply:ManipulateBoneAngles(ply:LookupBone("ValveBiped.Bip01_R_Thigh"), Angle(45,0,0)) ply:ManipulateBoneAngles(ply:LookupBone("ValveBiped.Bip01_L_Thigh"), Angle(-45,0,0)) end, OnExitFunc = function(ply) if not ply:LookupBone("ValveBiped.Bip01_R_Thigh") then return end ply:ManipulateBoneAngles(ply:LookupBone("ValveBiped.Bip01_R_Thigh"), Angle(0,0,0)) ply:ManipulateBoneAngles(ply:LookupBone("ValveBiped.Bip01_L_Thigh"), Angle(0,0,0)) end, FindAng = 0, }, { Pos = Vector(0, 8, -18), Ang = Angle(0, 0, 0), FindAng = 180, }, } local lookup = {} for k,v in pairs(SittingOnPlayerPoses) do table.insert(lookup, {v.FindAng, v}) table.insert(lookup, {v.FindAng + 360, v}) table.insert(lookup, {v.FindAng - 360, v}) end local function FindPose(this,me) local avec = me:GetAimVector() avec.z = 0 avec:Normalize() local evec = this:GetRight() evec.z = 0 evec:Normalize() local derp = avec:Dot(evec) local avec2 = me:GetAimVector() avec2.z = 0 avec2:Normalize() local evec2 = this:GetForward() evec2.z = 0 evec2:Normalize() local herp = avec2:Dot(evec2) local v = Vector(derp,herp,0) local a = v:Angle() local ang = a.y ang = ang + 90 + 180 ang = ang % 360 table.sort(lookup,function(aa,bb) return d(ang,aa[1]) < d(ang,bb[1]) end) return lookup[1][2] end function META.Sit(ply, EyeTrace, ang, parent, parentbone, func, exit, wantedAng) if EyeTrace == nil then EyeTrace = ply:GetEyeTrace() elseif type(EyeTrace) == "Vector" then return Sit(ply, EyeTrace, ang or Angle(0,0,0), parent, parentbone or 0, func, exit) end local valid, ent = SitAnywhere.ValidSitTrace(ply, EyeTrace) if not valid then return end local surfaceAng = EyeTrace.HitNormal:Angle() + Angle(-270, 0, 0) ang = surfaceAng if wantedAng and math.abs(surfaceAng.pitch) <= 15 then ent = EyeTrace.Entity if EyeTrace.HitWorld or not ent:IsPlayer() then if SitAnywhere.CheckValidAngForSit(EyeTrace.HitPos, EyeTrace.HitNormal:Angle(), wantedAng) then ang.yaw = wantedAng + 90 else return end end return Sit(ply, EyeTrace.HitPos - Vector(0, 0, 23), ang, ent, EyeTrace.PhysicsBone or 0) end if ix.config.Get("sitOnPlayers", true) then local veh if not EyeTrace.HitWorld and IsValid(EyeTrace.Entity) and EyeTrace.Entity:IsPlayer() and IsValid(EyeTrace.Entity:GetVehicle()) and EyeTrace.Entity:GetVehicle().playerdynseat then local safe = 256 veh = EyeTrace.Entity:GetVehicle() while IsValid(veh.SittingOnMe) and IsValid(veh.SittingOnMe:GetDriver()) and safe > 0 do safe = safe - 1 veh = veh.SittingOnMe end else for k, v in pairs(ents.FindInSphere(EyeTrace.HitPos, 12)) do local safe = 256 veh = v while IsValid(veh.SittingOnMe) and IsValid(veh.SittingOnMe:GetDriver()) and safe > 0 do safe = safe - 1 veh = veh.SittingOnMe end end end if IsValid(veh) and veh:GetClass() == "prop_vehicle_prisoner_pod" and veh:GetModel() ~= "models/vehicles/prisoner_pod_inner.mdl" and veh:GetDriver() and veh:GetDriver():IsValid() and not veh.PlayerSitOnPlayer then if IsValid(veh.holder) and veh.holder.GetTargetPlayer and veh.holder:GetTargetPlayer() == ply then return end if veh:GetDriver():GetInfoNum("sitting_allow_on_me", 1) == 0 then ply:ChatPrint(("%s отключил возможность сидеть на нём!"):format(veh:GetDriver():Name())) return false end local pose = FindPose(veh, ply) local pos = veh:GetDriver():GetPos() if veh.plyposhack then pos = veh:LocalToWorld(veh.plyposhack) end local vec, ang2 = LocalToWorld(pose.Pos, pose.Ang, pos, veh:GetAngles()) if veh:GetParent() == ply then return false end ent = Sit(ply, vec, ang2, veh, 0, pose.Func, pose.OnExitFunc) if ent and IsValid(ent) then ent.PlayerOnPlayer = true veh.SittingOnMe = ent end return true, ent end else for k, v in pairs(ents.FindInSphere(EyeTrace.HitPos, 5)) do if v.playerdynseat then return false end end end local shouldSitOnPlayer = (ply.IsFlying and ply:IsFlying()) or EyeTrace.Entity == ply:GetGroundEntity() or ply:GetMoveType() == MOVETYPE_NOCLIP if IsValid(EyeTrace.Entity) and EyeTrace.Entity:IsPlayer() and ix.config.Get("sitOnPlayerEntities", true) and shouldSitOnPlayer then ent = EyeTrace.Entity if IsValid(ent:GetVehicle()) then return end local min, max = ent:GetCollisionBounds() local zadjust = math.abs(min.z) + math.abs(max.z) local seatPos = ent:GetPos() + Vector(0, 0, 10 + zadjust / 2) local vehicle = Sit(ply, seatPos, ply:GetAngles(), ent, EyeTrace.PhysicsBone or 0) return vehicle end if math.abs(surfaceAng.pitch) <= 15 then ang = Angle() local sampleResolution = 24 local dists, distsang, ang_smallest_hori, smallest_hori = SitAnywhere.GetAreaProfile(EyeTrace.HitPos, sampleResolution, false) local infront = ((ang_smallest_hori or 0) + 180) % 360 local cancelSit, seat = hook.Run("HandleSit", ply, dists, EyeTrace) if cancelSit then return seat end if ang_smallest_hori and distsang[infront].Hit and distsang[infront].Distance > 14 and smallest_hori <= 16 then local hori = distsang[ang_smallest_hori].HorizontalTrace ang.yaw = (hori.HitNormal:Angle().yaw - 90) return Sit(ply, EyeTrace.HitPos - Vector(0, 0, 23), ang, (not EyeTrace.HitWorld) and EyeTrace.Entity, EyeTrace.PhysicsBone or 0) else table.sort(dists, function(a,b) return b.Distance < a.Distance end) local wants = {} local eyeang = ply:EyeAngles() + Angle(0, 180, 0) for I = 1, #dists do local trace = dists[I] local behind = distsang[(trace.ang + 180) % 360] if behind.Distance2 > 3 then table.insert(wants, { cost = math.abs(eyeang.yaw - trace.ang), ang = trace.ang, }) end end table.sort(wants,function(a,b) return b.cost > a.cost end) if #wants == 0 then return end ang.yaw = (wants[1].ang - 90) return Sit(ply, EyeTrace.HitPos - Vector(0, 0, 23), ang, (not EyeTrace.HitWorld) and EyeTrace.Entity, EyeTrace.PhysicsBone or 0) end end end local function checkAllowSit(ply) local allowSit = hook.Run("ShouldAllowSit", ply) local bottom, top = ply:GetHull() local diff = top.Z - bottom.Z local trace = util.QuickTrace(ply:GetPos(), Vector(0, 0, diff), player.GetAll()) if not ix.config.Get("sitAllowTightPlaces", false) and trace.HitWorld then return false end if allowSit == false or allowSit == true then return allowSit end if ix.config.Get("sitAdminOnly", false) and not ply:IsAdmin() then return false end return true end local function sitcmd(ply) if not IsValid(ply) then return end if ply:InVehicle() then return end if not checkAllowSit(ply) then return end local now = CurTime() if NextUse[ply] > now then return end if ply:Sit() then NextUse[ply] = now + 1 else NextUse[ply] = now + 0.1 end end concommand.Add("sit", function(ply, cmd, args) sitcmd(ply) end) local function UndoSitting(ply) if not IsValid(ply) then return end local prev = ply.sitting_allowswep if prev ~= nil then ply.sitting_allowswep = nil ply:SetAllowWeaponsInVehicle(prev) end if ix.config.Get("sitPlayerDamage", false) then ply:SetCollisionGroup(COLLISION_GROUP_PLAYER) ply:CollisionRulesChanged() end if ply.seatExit then ply.seatExit(ply) ply.seatExit = nil end end -- Хуки function PLUGIN:CanExitVehicle(seat, ply) if not IsValid(seat) or not IsValid(ply) then return end if not seat.playerdynseat then return end if CurTime() < NextUse[ply] then return false end end function PLUGIN:PlayerLeaveVehicle(ply, seat) if not IsValid(seat) or not IsValid(ply) then return end if not seat.playerdynseat then return end local oldpos = seat:LocalToWorld(seat.oldpos) ply:SetPos(oldpos) if ply.UnStuck then ply:UnStuck() end for _, v in next, seat:GetChildren() do if IsValid(v) and v.playerdynseat and IsValid(v.sittingPly) then v.sittingPly:ExitVehicle() end end SafeRemoveEntityDelayed(seat, 1) UndoSitting(ply) end function PLUGIN:AllowPlayerPickup(ply) if ply:KeyDown(IN_WALK) then return false end end function PLUGIN:PlayerDeath(pl) local veh = pl:GetVehicle() if IsValid(veh) and veh.playerdynseat then SafeRemoveEntity(veh) end for k, v in next, pl:GetChildren() do if IsValid(v) and v.playerdynseat and IsValid(v.sittingPly) then v.sittingPly:ExitVehicle() end end end function PLUGIN:PlayerEnteredVehicle(pl, veh) for k,v in next, pl:GetChildren() do if IsValid(v) and v.playerdynseat and IsValid(v.sittingPly) then v.sittingPly:ExitVehicle() end end DropEntityIfHeld(veh) local parent = veh:GetParent() if IsValid(parent) then DropEntityIfHeld(parent) end end function PLUGIN:OnPlayerSit(ply, pos, ang, parent, parentbone, vehicle) if IsValid(parent) and parent ~= game.GetWorld() then if parent:GetModel():sub(1, 6) ~= "models" then return false end if parent:IsPlayer() then if not ix.config.Get("sitOnPlayerEntities", true) then return false end if parent:GetInfoNum("sitting_allow_on_me", 1) == 0 then ply:ChatPrint(("%s отключил возможность сидеть на нём!"):format(parent:Name())) return false end if IsValid(parent:GetVehicle()) then return false end end end end function PLUGIN:EntityRemoved(ent) if ent.playerdynseat and IsValid(ent.sittingPly) then UndoSitting(ent.sittingPly) end for _, v in next, ent:GetChildren() do if IsValid(v) and v.playerdynseat and IsValid(v.sittingPly) then v.sittingPly:ExitVehicle() end end end function PLUGIN:PhysgunPickup(ply, ent) if ix.config.Get("sitAntiPropSurf", true) then local function CheckSeat(checkPly, checkEnt, tbl) if not checkPly:InVehicle() then return true end local vehicle = checkPly:GetVehicle() local parent = vehicle.parent if parent == checkEnt then return false end for _,v in next, checkEnt:GetChildren() do if IsValid(v) and not tbl[v] then tbl[v] = true if v ~= checkEnt and CheckSeat(checkPly, v, tbl) == false then return false end end end local cEnts = constraint.GetAllConstrainedEntities(checkEnt) if cEnts then for _,v in next, cEnts do if IsValid(v) and not tbl[v] then tbl[v] = true if v ~= checkEnt and CheckSeat(checkPly, v, tbl) == false then return false end end end end end if IsValid(ply:GetVehicle()) and ply:GetVehicle().playerdynseat then if CheckSeat(ply, ent, {}) == false then return false end end end end function PLUGIN:CanTool(ply, tr) if ix.config.Get("sitAntiToolAbuse", true) and IsValid(tr.Entity) then local function CheckSeat(checkPly, checkEnt, tbl) if not checkPly:InVehicle() then return true end local vehicle = checkPly:GetVehicle() local parent = vehicle.parent if parent == checkEnt then return false end for _,v in next, checkEnt:GetChildren() do if IsValid(v) and not tbl[v] then tbl[v] = true if v ~= checkEnt and CheckSeat(checkPly, v, tbl) == false then return false end end end local cEnts = constraint.GetAllConstrainedEntities(checkEnt) if cEnts then for _,v in next, cEnts do if IsValid(v) and not tbl[v] then tbl[v] = true if v ~= checkEnt and CheckSeat(checkPly, v, tbl) == false then return false end end end end end if IsValid(ply:GetVehicle()) and ply:GetVehicle().playerdynseat then if CheckSeat(ply, tr.Entity, {}) == false then return false end end end end timer.Create("SitAnywhere_RemoveSeats", 15, 0, function() for k,v in pairs(ents.FindByClass("prop_vehicle_prisoner_pod")) do if v.playerdynseat and (not IsValid(v.sittingPly) or v:GetDriver() == nil or not v:GetDriver():IsValid() or v:GetDriver():GetVehicle() ~= v) then v:Remove() end end end)