├── LICENSE ├── README.md └── physcrashguard ├── client ├── gradualunfreezing.lua └── settings.lua ├── constraints.lua ├── detection.lua ├── freezedupesonpaste.lua ├── gradualunfreezing.lua ├── iterator.lua ├── main.lua ├── resolving.lua ├── restoring.lua └── util.lua /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 noaccessl 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gmod-PhysicsCrashGuard 2 | Effectively detects and prevents crash attempts using physics objects. 3 | Simple and lightweight. 4 | 5 | [Showcase](https://youtu.be/FBUrrf0f-Ns) 6 | 7 | [Workshop Page](https://steamcommunity.com/sharedfiles/filedetails/?id=3148349097) 8 | -------------------------------------------------------------------------------- /physcrashguard/client/gradualunfreezing.lua: -------------------------------------------------------------------------------- 1 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 2 | 3 | Display the process of unfreezing objects 4 | 5 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 6 | 7 | 8 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 9 | Prepare 10 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 11 | -- 12 | -- Globals 13 | -- 14 | local physcrashguard = physcrashguard 15 | 16 | 17 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 18 | Unfreezing enums 19 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 20 | local UNFREEZE_START = 0 21 | local UNFREEZE_ABORT = 1 22 | local UNFREEZE_PROGRESS = 2 23 | local UNFREEZE_DONE = 3 24 | 25 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 26 | Unfreezing data 27 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 28 | physcrashguard.m_Unfreezing = physcrashguard.m_Unfreezing or { 29 | 30 | Status = 0; 31 | Entities = {} 32 | 33 | } 34 | 35 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 36 | Purpose: Simple interface for managing unfreezing status & entities 37 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 38 | local IUnfreezingQueue = {} 39 | do 40 | 41 | IUnfreezingQueue.__index = IUnfreezingQueue 42 | IUnfreezingQueue.__tostring = function( self ) return Format( 'IUnfreezingQueue: %p', self ) end 43 | 44 | -- Get the status 45 | function IUnfreezingQueue:GetStatus() return physcrashguard.m_Unfreezing.Status end 46 | 47 | -- Get all entities 48 | function IUnfreezingQueue:GetAll() return physcrashguard.m_Unfreezing.Entities end 49 | 50 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 51 | SetStatus 52 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 53 | function IUnfreezingQueue:SetStatus( iStatus ) 54 | 55 | physcrashguard.m_Unfreezing.Status = iStatus 56 | 57 | end 58 | 59 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 60 | UpdateStatus 61 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 62 | function IUnfreezingQueue:UpdateStatus( text, flFraction ) 63 | 64 | if ( text == nil ) then 65 | notification.Kill( 'unfreezing' ) 66 | else 67 | notification.AddProgress( 'unfreezing', text, flFraction ) 68 | end 69 | 70 | end 71 | 72 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 73 | Start 74 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 75 | function IUnfreezingQueue:Start() 76 | 77 | self:Clear() 78 | self:SetStatus( UNFREEZE_START ) 79 | 80 | self:UpdateStatus( Format( language.GetPhrase( 'hint.unfrozeX' ), 0 ) .. '...', 0 ) 81 | 82 | end 83 | 84 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 85 | Abort 86 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 87 | function IUnfreezingQueue:Abort() 88 | 89 | self:SetStatus( UNFREEZE_ABORT ) 90 | 91 | timer.Simple( 1, function() 92 | 93 | if ( self:GetStatus() == UNFREEZE_ABORT ) then 94 | self:Clear() 95 | end 96 | 97 | end ) 98 | 99 | self:UpdateStatus( Format( language.GetPhrase( 'hint.unfrozeX' ), 0 ) .. '...', 0 ) 100 | self:UpdateStatus( nil ) 101 | 102 | end 103 | 104 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 105 | PropelForward 106 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 107 | function IUnfreezingQueue:PropelForward( flFraction, pEntity ) 108 | 109 | self:SetStatus( UNFREEZE_PROGRESS ) 110 | local iUnfrozeObjects = self:Add( pEntity ) 111 | 112 | self:UpdateStatus( Format( language.GetPhrase( 'hint.unfrozeX' ), iUnfrozeObjects ) .. '...', flFraction ) 113 | 114 | end 115 | 116 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 117 | Finish 118 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 119 | function IUnfreezingQueue:Finish( iUnfrozeObjects ) 120 | 121 | self:SetStatus( UNFREEZE_DONE ) 122 | self:Clear() 123 | 124 | self:UpdateStatus( Format( language.GetPhrase( 'hint.unfrozeX' ), iUnfrozeObjects ) .. '.', 1 ) 125 | self:UpdateStatus( nil ) 126 | 127 | if ( GAMEMODE.UnfrozeObjects ) then 128 | GAMEMODE:UnfrozeObjects( iUnfrozeObjects ) 129 | end 130 | 131 | end 132 | 133 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 134 | Add 135 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 136 | function IUnfreezingQueue:Add( pEntity ) 137 | 138 | return table.insert( physcrashguard.m_Unfreezing.Entities, pEntity ) 139 | 140 | end 141 | 142 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 143 | Clear 144 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 145 | function IUnfreezingQueue:Clear() 146 | 147 | table.Empty( physcrashguard.m_Unfreezing.Entities ) 148 | 149 | end 150 | 151 | end 152 | 153 | if ( not physcrashguard.UnfreezingQueue ) then 154 | 155 | physcrashguard.UnfreezingQueue = newproxy() 156 | debug.setmetatable( physcrashguard.UnfreezingQueue, IUnfreezingQueue ) 157 | 158 | end 159 | 160 | 161 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 162 | Purpose: Display the process 163 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 164 | local g_colUnfreezing = Color( 76, 255, 255 ) 165 | local g_colAbort = Color( 255 - g_colUnfreezing.r, 255 - g_colUnfreezing.g, 255 - g_colUnfreezing.b ) -- Let's have inverted color 166 | 167 | local g_colDisplaying = g_colUnfreezing 168 | 169 | hook.Add( 'PreDrawHalos', 'PhysicsCrashGuard_GradualUnfreezing', function() 170 | 171 | local UnfreezingEntities = physcrashguard.UnfreezingQueue:GetAll() 172 | 173 | if ( #UnfreezingEntities == 0 ) then 174 | return 175 | end 176 | 177 | local iStatus = physcrashguard.UnfreezingQueue:GetStatus() 178 | 179 | if ( iStatus == UNFREEZE_ABORT ) then 180 | g_colDisplaying = g_colAbort 181 | else 182 | g_colDisplaying = g_colUnfreezing 183 | end 184 | 185 | halo.Add( UnfreezingEntities, g_colDisplaying, 2, 2, 1, true, true ) 186 | 187 | end ) 188 | 189 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 190 | Network 191 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 192 | net.Receive( 'physcrashguard.Unfreeze', function() 193 | 194 | local iType = net.ReadUInt( 2 ) 195 | 196 | if ( iType == UNFREEZE_START ) then 197 | 198 | physcrashguard.UnfreezingQueue:Start() 199 | 200 | elseif ( iType == UNFREEZE_ABORT ) then 201 | 202 | physcrashguard.UnfreezingQueue:Abort() 203 | 204 | elseif ( iType == UNFREEZE_PROGRESS ) then 205 | 206 | local flFraction = net.ReadFloat() 207 | local pEntity = net.ReadEntity() 208 | 209 | physcrashguard.UnfreezingQueue:PropelForward( flFraction, pEntity ) 210 | 211 | elseif ( iType == UNFREEZE_DONE ) then 212 | 213 | local iUnfrozeObjects = net.ReadUInt( 12 ) 214 | physcrashguard.UnfreezingQueue:Finish( iUnfrozeObjects ) 215 | 216 | end 217 | 218 | -- Update colors 219 | local cl_weaponcolor = GetConVar( 'cl_weaponcolor' ) 220 | 221 | if ( cl_weaponcolor ) then 222 | g_colUnfreezing = Vector( cl_weaponcolor:GetString() ):ToColor() 223 | else 224 | g_colUnfreezing = Color( 76, 255, 255 ) 225 | end 226 | 227 | g_colAbort = Color( 255 - g_colUnfreezing.r, 255 - g_colUnfreezing.g, 255 - g_colUnfreezing.b ) 228 | 229 | end ) 230 | -------------------------------------------------------------------------------- /physcrashguard/client/settings.lua: -------------------------------------------------------------------------------- 1 | 2 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 3 | Purpose: Add some settings to the Spawn Menu 4 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 5 | local function SetupSettings( Page ) 6 | 7 | Page:AddControl( 'Slider', { 8 | 9 | Label = 'Hang detection (ms)'; 10 | Command = 'physcrashguard_hangdetection'; 11 | Type = 'Float'; 12 | Min = 1; 13 | Max = 100 14 | 15 | } ) 16 | Page:ControlHelp( 'What delay in physics simulation will be considered as a physics hang' ) 17 | 18 | Page:AddControl( 'CheckBox', { Label = 'Delete on resolve'; Command = 'physcrashguard_delete' } ) 19 | Page:ControlHelp( 'Experimental. Should entities to resolve be deleted? Won\'t apply to ragdolls.' ) 20 | 21 | Page:AddControl( 'CheckBox', { Label = 'Freeze dupes on paste'; Command = 'physcrashguard_freezedupesonpaste' } ) 22 | Page:ControlHelp( 'Should dupes be freezed on paste?' ) 23 | 24 | end 25 | 26 | hook.Add( 'PopulateToolMenu', 'PhysicsCrashGuard_Settings', function() 27 | 28 | spawnmenu.AddToolMenuOption( 29 | 30 | 'Utilities', 31 | 'Admin', 32 | 'physcrashguard', 33 | 'Physics Crash Guard', 34 | '', 35 | '', 36 | SetupSettings 37 | 38 | ) 39 | 40 | end ) 41 | -------------------------------------------------------------------------------- /physcrashguard/constraints.lua: -------------------------------------------------------------------------------- 1 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 2 | 3 | Adjustments to some constraints in the game 4 | 5 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 6 | 7 | 8 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 9 | Prepare 10 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 11 | -- 12 | -- Metatables 13 | -- 14 | local EntityMeta = FindMetaTable( 'Entity' ) 15 | 16 | -- 17 | -- Metamethods: Entity 18 | -- 19 | local GetEntityTable = EntityMeta.GetTable 20 | local IsEntityValid = EntityMeta.IsValid 21 | local IsWorld = EntityMeta.IsWorld 22 | 23 | -- 24 | -- Functions 25 | -- 26 | local subsequent = ipairs( {} ) 27 | local next = pairs( {} ) 28 | 29 | -- 30 | -- Globals 31 | -- 32 | local physcrashguard = physcrashguard 33 | 34 | 35 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 36 | Purpose: Fixes sliders by limiting the distance between two connected objects using rope 37 | 38 | Note: 39 | Sliders will crash the game if: 40 | 1. Two connected objects are too far away. 41 | 2. They are bitching too much. (Consequence from the first point.) 42 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 43 | local MAX_DISTANCE = 6656 44 | 45 | hook.Add( 'OnEntityCreated', 'PhysicsCrashGuard_FixSliders', function( pEntity ) 46 | 47 | timer.Simple( 0, function() 48 | 49 | if ( not pEntity:IsValid() ) then 50 | return 51 | end 52 | 53 | if ( pEntity:GetClass() == 'phys_slideconstraint' ) then 54 | 55 | local Ent1 = pEntity.Ent1 56 | 57 | if ( not IsValid( Ent1 ) ) then 58 | return 59 | end 60 | 61 | local Ent2 = pEntity.Ent2 62 | 63 | if ( not IsValid( Ent2 ) ) then 64 | return 65 | end 66 | 67 | local vecPos1 = Ent1:GetPos() 68 | local vecPos2 = Ent2:GetPos() 69 | 70 | local flCurrentDist = vecPos1:Distance( vecPos2 ) 71 | flCurrentDist = math.min( flCurrentDist, MAX_DISTANCE ) 72 | 73 | local vecDir = ( vecPos2 - vecPos1 ):Angle():Forward() 74 | local vecLimitedPos = vecPos1 + vecDir * flCurrentDist 75 | 76 | Ent2:SetPos( vecLimitedPos ) 77 | 78 | local flAddLength = MAX_DISTANCE - flCurrentDist 79 | 80 | constraint.Rope( 81 | 82 | Ent1, 83 | Ent2, 84 | 0, -- Bone 1 85 | 0, -- Bone 2 86 | vector_origin, -- Local pos 1 87 | vector_origin, -- Local pos 2 88 | flCurrentDist, -- Length 89 | flAddLength, -- Additional length 90 | 0, -- Force limit 91 | 0 -- Width 92 | 93 | ) 94 | 95 | end 96 | 97 | end ) 98 | 99 | end ) 100 | 101 | 102 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 103 | Purpose: Optimized constraint.HasConstraints 104 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 105 | function physcrashguard.HasConstraints( pEntity ) 106 | 107 | if ( not physcrashguard.util.IsEntity( pEntity ) ) then 108 | return false 109 | end 110 | 111 | local pEntity_t = GetEntityTable( pEntity ) 112 | local constraints = pEntity_t.Constraints 113 | 114 | if ( not constraints ) then 115 | return false 116 | end 117 | 118 | local bHas = false 119 | 120 | for index, pConstraint in next, constraints do 121 | 122 | if ( not IsEntityValid( pConstraint ) ) then 123 | constraints[index] = nil 124 | else 125 | bHas = true 126 | end 127 | 128 | end 129 | 130 | return bHas 131 | 132 | end 133 | 134 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 135 | Purpose: Optimized (and reduced) constraint.GetTable 136 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 137 | local HasConstraints = physcrashguard.HasConstraints 138 | 139 | function physcrashguard.GetConstraintsData( pEntity ) 140 | 141 | if ( not HasConstraints( pEntity ) ) then 142 | return false 143 | end 144 | 145 | local constraintsdata = { [0] = 0 } 146 | 147 | for _, pConstraint in next, GetEntityTable( pEntity ).Constraints do 148 | 149 | local pConstraint_t = GetEntityTable( pConstraint ) 150 | 151 | local index = constraintsdata[0] + 1 152 | constraintsdata[index] = pConstraint_t 153 | constraintsdata[0] = index 154 | 155 | end 156 | 157 | constraintsdata[0] = nil 158 | return constraintsdata 159 | 160 | end 161 | 162 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 163 | Purpose: Optimized constraint.GetAllConstrainedEntities 164 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 165 | local GetConstraintsData = physcrashguard.GetConstraintsData 166 | 167 | local function GetAllConstrainedEntitiesSequentially( pEntity, output, map ) 168 | 169 | output = output or { [0] = 0 } 170 | map = map or {} 171 | 172 | if ( not IsEntityValid( pEntity ) ) then 173 | return 174 | end 175 | 176 | if ( map[pEntity] ) then 177 | return 178 | end 179 | 180 | local index = output[0] 181 | index = index + 1 182 | 183 | output[index] = pEntity 184 | map[pEntity] = true 185 | 186 | output[0] = index 187 | 188 | local ret = GetConstraintsData( pEntity ) 189 | 190 | if ( ret ~= false ) then 191 | 192 | local constraintsdata = ret 193 | 194 | for _, pConstraint_t in subsequent, constraintsdata, 0 do 195 | 196 | for i = 1, 2 do 197 | 198 | local pConstrained = pConstraint_t[ 'Ent' .. i ] 199 | 200 | if ( not IsWorld( pConstrained ) ) then 201 | GetAllConstrainedEntitiesSequentially( pConstrained, output, map ) 202 | end 203 | 204 | end 205 | 206 | end 207 | 208 | end 209 | 210 | return output 211 | 212 | end 213 | 214 | physcrashguard.GetAllConstrainedEntitiesSequentially = GetAllConstrainedEntitiesSequentially 215 | -------------------------------------------------------------------------------- /physcrashguard/detection.lua: -------------------------------------------------------------------------------- 1 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 2 | 3 | Detecting physics hang & Dealing with problematic objects 4 | 5 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 6 | 7 | 8 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 9 | Prepare 10 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 11 | -- 12 | -- Metamethods: Entity; PhysObj 13 | -- 14 | local GetEntityTable = FindMetaTable( 'Entity' ).GetTable 15 | local IsRagdoll = FindMetaTable( 'Entity' ).IsRagdoll 16 | 17 | local VPhysicsGetEntity = FindMetaTable( 'PhysObj' ).GetEntity 18 | local VPhysicsIsPenetrating = FindMetaTable( 'PhysObj' ).IsPenetrating 19 | 20 | -- 21 | -- Globals 22 | -- 23 | local physcrashguard = physcrashguard 24 | 25 | local IsThereHang = physcrashguard.IsThereHang 26 | local PhysIterator = physcrashguard.Iterator 27 | local Resolve = physcrashguard.Resolve 28 | local ResolveRagdoll = physcrashguard.ResolveRagdoll 29 | 30 | 31 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 32 | Purpose: Detect hang and deal with it 33 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 34 | function physcrashguard.DetectHang() 35 | 36 | if ( IsThereHang() ) then 37 | 38 | for _, pPhysObj in PhysIterator() do 39 | 40 | local pEntity = VPhysicsGetEntity( pPhysObj ) 41 | 42 | if ( IsRagdoll( pEntity ) ) then 43 | 44 | local pPhysPart = pPhysObj 45 | local pRagdoll = pEntity 46 | 47 | if ( VPhysicsIsPenetrating( pPhysPart ) ) then 48 | ResolveRagdoll( pPhysPart, pRagdoll, GetEntityTable( pRagdoll ) ) 49 | end 50 | 51 | else 52 | 53 | if ( VPhysicsIsPenetrating( pPhysObj ) ) then 54 | Resolve( pPhysObj, pEntity, GetEntityTable( pEntity ) ) 55 | end 56 | 57 | end 58 | 59 | end 60 | 61 | end 62 | 63 | end 64 | 65 | hook.Add( 'Think', 'PhysicsCrashGuard_DetectHang', function() 66 | 67 | physcrashguard.DetectHang() 68 | 69 | end ) 70 | -------------------------------------------------------------------------------- /physcrashguard/freezedupesonpaste.lua: -------------------------------------------------------------------------------- 1 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 2 | 3 | A submodule responsible for freezing dupes on creation 4 | 5 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 6 | 7 | 8 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 9 | Prepare 10 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 11 | -- 12 | -- Metamethods: Entity; PhysObj 13 | -- 14 | local EntityMeta = FindMetaTable( 'Entity' ) 15 | 16 | local IsRagdoll = EntityMeta.IsRagdoll 17 | local GetPhysicsObject = EntityMeta.GetPhysicsObject 18 | local GetPhysicsObjectCount = EntityMeta.GetPhysicsObjectCount 19 | local GetPhysicsObjectNum = EntityMeta.GetPhysicsObjectNum 20 | 21 | local PhysObjMeta = FindMetaTable( 'PhysObj' ) 22 | 23 | local VPhysicsIsValid = PhysObjMeta.IsValid 24 | local VPhysicsEnableMotion = PhysObjMeta.EnableMotion 25 | local VPhysicsSleep = PhysObjMeta.Sleep 26 | 27 | -- 28 | -- Functions 29 | -- 30 | local subsequent = ipairs( {} ) 31 | 32 | 33 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 34 | A setting 35 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 36 | local physcrashguard_freezedupesonpaste = CreateConVar( 37 | 38 | 'physcrashguard_freezedupesonpaste', 39 | '1', 40 | 41 | FCVAR_ARCHIVE, 42 | 43 | 'Should dupes be freezed on paste?', 44 | 0, 1 45 | 46 | ) 47 | 48 | local g_bFreezeDupesOnPaste = physcrashguard_freezedupesonpaste:GetBool() 49 | 50 | cvars.AddChangeCallback( 'physcrashguard_hangdetection', function( _, _, value ) 51 | 52 | g_bFreezeDupesOnPaste = tobool( value ) 53 | 54 | end, 'Main' ) 55 | 56 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 57 | Purpose: Catch & freeze just pasted dupes 58 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 59 | local function FreezeEntities( listEntites ) 60 | 61 | for _, pEntity in subsequent, listEntites, 0 do 62 | 63 | if ( IsRagdoll( pEntity ) ) then 64 | 65 | for numObj = 0, GetPhysicsObjectCount( pEntity ) - 1 do 66 | 67 | local pPhysObj = GetPhysicsObjectNum( pEntity, numObj ) 68 | 69 | if ( VPhysicsIsValid( pPhysObj ) ) then 70 | 71 | VPhysicsEnableMotion( pPhysObj, false ) 72 | VPhysicsSleep( pPhysObj ) 73 | 74 | end 75 | 76 | end 77 | 78 | else 79 | 80 | local pPhysObj = GetPhysicsObject( pEntity ) 81 | 82 | if ( VPhysicsIsValid( pPhysObj ) ) then 83 | 84 | VPhysicsEnableMotion( pPhysObj, false ) 85 | VPhysicsSleep( pPhysObj ) 86 | 87 | end 88 | 89 | end 90 | 91 | end 92 | 93 | end 94 | 95 | hook.Add( 'CanCreateUndo', 'PhysicsCrashGuard_FreezeDupesOnPaste', function( pl, tblUndo ) 96 | 97 | if ( tblUndo.Name ~= 'Duplicator' ) then 98 | return 99 | end 100 | 101 | if ( not g_bFreezeDupesOnPaste ) then 102 | return 103 | end 104 | 105 | FreezeEntities( tblUndo.Entities ) 106 | 107 | end ) 108 | -------------------------------------------------------------------------------- /physcrashguard/gradualunfreezing.lua: -------------------------------------------------------------------------------- 1 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 2 | 3 | Gradual Unfreezing 4 | 5 | Got inspired by this addon https://steamcommunity.com/sharedfiles/filedetails/?id=3288286594 6 | Credits to the author of the addon. 7 | 8 | Not my intention to replace the addon, but to implement the concept here so that there's no need to install both. 9 | You can install both, no problem with it. 10 | 11 | For compatibility sake it will not work with that addon. 12 | 13 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 14 | 15 | 16 | local DELAY = 0.03 17 | 18 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 19 | Prepare 20 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 21 | -- 22 | -- Metatables: Entity; PhysObj, Player 23 | -- 24 | local EntityMeta = FindMetaTable( 'Entity' ) 25 | 26 | local IsUnfreezable = EntityMeta.GetUnFreezable 27 | 28 | local GetPhysicsObjectCount = EntityMeta.GetPhysicsObjectCount 29 | local GetPhysicsObjectNum = EntityMeta.GetPhysicsObjectNum 30 | 31 | local GetEntityTable = EntityMeta.GetTable 32 | 33 | local IsValidEntity = EntityMeta.IsValid 34 | 35 | local PhysObj = FindMetaTable( 'PhysObj' ) 36 | 37 | local VPhysicsIsMoveable = PhysObj.IsMoveable 38 | local VPhysicsIsValid = PhysObj.IsValid 39 | local VPhysicsEnableMotion = PhysObj.EnableMotion 40 | local VPhysicsSleep = PhysObj.Sleep 41 | local VPhysicsWake = PhysObj.Wake 42 | local VPhysicsGetEntity = PhysObj.GetEntity 43 | 44 | local GetAimTrace = FindMetaTable( 'Player' ).GetEyeTrace 45 | local HasPlayerReleasedKey = FindMetaTable( 'Player' ).KeyReleased 46 | 47 | -- 48 | -- Functions 49 | -- 50 | local subsequent = ipairs( {} ) 51 | 52 | local GetCurTime = CurTime 53 | 54 | local GamemodeCall = gamemode.Call 55 | 56 | -- 57 | -- Globals, Utilities 58 | -- 59 | local net = net 60 | 61 | 62 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 63 | Unfreezing enums 64 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 65 | local UNFREEZE_START = 0 66 | local UNFREEZE_ABORT = 1 67 | local UNFREEZE_PROGRESS = 2 68 | local UNFREEZE_DONE = 3 69 | 70 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 71 | Network 72 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 73 | util.AddNetworkString( 'physcrashguard.Unfreeze' ) 74 | 75 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 76 | Purpose: Collect unfreezable objects 77 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 78 | local GetAllConstrainedEntitiesSequentially = physcrashguard.GetAllConstrainedEntitiesSequentially 79 | 80 | local function CollectUnfreezable( pl, pLookupEntity ) 81 | 82 | local tblUnfreezing = { [0] = 0 } 83 | local tblConstrainedEntities = GetAllConstrainedEntitiesSequentially( pLookupEntity ) 84 | 85 | for _, pEntity in subsequent, tblConstrainedEntities, 0 do 86 | 87 | if ( IsUnfreezable( pEntity ) ) then 88 | continue 89 | end 90 | 91 | local numPhysObjs = GetPhysicsObjectCount( pEntity ) 92 | 93 | for numObj = 1, numPhysObjs do 94 | 95 | local pPhysObj = GetPhysicsObjectNum( pEntity, numObj - 1 ) 96 | 97 | if ( not GetEntityTable( pEntity ).m_PhysHang and VPhysicsIsMoveable( pPhysObj ) ) then 98 | continue 99 | end 100 | 101 | if ( not GamemodeCall( 'CanPlayerUnfreeze', pl, pEntity, pPhysObj ) ) then 102 | continue 103 | end 104 | 105 | local index = tblUnfreezing[0] 106 | index = index + 1 107 | 108 | tblUnfreezing[index] = pPhysObj 109 | tblUnfreezing[0] = index 110 | 111 | end 112 | 113 | end 114 | 115 | return tblUnfreezing 116 | 117 | end 118 | 119 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 120 | Purpose: Start gradual unfreezing 121 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 122 | function physcrashguard.StartGradualUnfreezing( pl ) 123 | 124 | -- Compatibility: Physgun Unfreeze Over Time 125 | if ( _G.puot ) then 126 | return 127 | end 128 | 129 | local traceAim = GetAimTrace( pl ) 130 | local pFacingEntity = traceAim.Entity 131 | 132 | if ( traceAim.HitNonWorld and IsValidEntity( pFacingEntity ) ) then 133 | 134 | local pl_t = GetEntityTable( pl ) 135 | 136 | local tblPhysObjs = CollectUnfreezable( pl, pFacingEntity ) 137 | 138 | if ( tblPhysObjs[0] == 0 ) then 139 | return 140 | end 141 | 142 | pl_t.m_Unfreezing = { 143 | 144 | m_tPhysObjs = tblPhysObjs; 145 | m_iNextTime = GetCurTime() + DELAY; 146 | m_iCurrent = 1 147 | 148 | } 149 | 150 | net.Start( 'physcrashguard.Unfreeze' ) 151 | net.WriteUInt( UNFREEZE_START, 2 ) 152 | net.Send( pl ) 153 | 154 | end 155 | 156 | return false 157 | 158 | end 159 | 160 | 161 | local StartGradualUnfreezing = physcrashguard.StartGradualUnfreezing 162 | 163 | hook.Add( 'OnPhysgunReload', 'PhysicsCrashGuard_GradualUnfreezing', function( pPhysGun, pl ) 164 | 165 | local ret = StartGradualUnfreezing( pl ) 166 | 167 | if ( ret ~= nil ) then 168 | return ret 169 | end 170 | 171 | end ) 172 | 173 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 174 | Purpose: Process gradual unfreezing 175 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 176 | local TryToRestore = physcrashguard.TryToRestore 177 | 178 | function physcrashguard.ProcessGradualUnfreezing( pl ) 179 | 180 | -- Compatibility: Physgun Unfreeze Over Time 181 | if ( _G.puot ) then 182 | return 183 | end 184 | 185 | local pl_t = GetEntityTable( pl ) 186 | local Unfreezing = pl_t.m_Unfreezing 187 | 188 | if ( not Unfreezing ) then 189 | return 190 | end 191 | 192 | local flCurTime = GetCurTime() 193 | 194 | local tblPhysObjs = Unfreezing.m_tPhysObjs 195 | 196 | -- 197 | -- Abort the process 198 | -- 199 | if ( HasPlayerReleasedKey( pl, IN_RELOAD ) ) then 200 | 201 | -- 202 | -- Freeze back the objects 203 | -- 204 | for i, pPhysObj in subsequent, tblPhysObjs, 0 do 205 | 206 | if ( VPhysicsIsValid( pPhysObj ) and VPhysicsIsMoveable( pPhysObj ) ) then 207 | 208 | VPhysicsEnableMotion( pPhysObj, false ) 209 | VPhysicsSleep( pPhysObj ) 210 | 211 | end 212 | 213 | end 214 | 215 | pl_t.m_Unfreezing = nil 216 | 217 | net.Start( 'physcrashguard.Unfreeze' ) 218 | net.WriteUInt( UNFREEZE_ABORT, 2 ) 219 | net.Send( pl ) 220 | 221 | return 222 | 223 | end 224 | 225 | local iTotal = tblPhysObjs[0] 226 | local iCurrent = Unfreezing.m_iCurrent 227 | 228 | -- 229 | -- Stop unfreezing 230 | -- 231 | if ( iCurrent > iTotal ) then 232 | 233 | pl_t.m_Unfreezing = nil 234 | 235 | net.Start( 'physcrashguard.Unfreeze' ) 236 | net.WriteUInt( UNFREEZE_DONE, 2 ) 237 | net.WriteUInt( iTotal, 12 ) 238 | net.Send( pl ) 239 | 240 | return 241 | 242 | end 243 | 244 | -- 245 | -- Gradually unfreeze the objects 246 | -- 247 | local iNextTime = Unfreezing.m_iNextTime 248 | 249 | if ( iNextTime < flCurTime ) then 250 | 251 | local pPhysObj = tblPhysObjs[iCurrent] 252 | 253 | -- 254 | -- Terminate the process if one of the entities was removed 255 | -- 256 | if ( not VPhysicsIsValid( pPhysObj ) ) then 257 | 258 | pl_t.m_Unfreezing = nil 259 | 260 | net.Start( 'physcrashguard.Unfreeze' ) 261 | net.WriteUInt( UNFREEZE_ABORT, 2 ) 262 | net.Send( pl ) 263 | 264 | return 265 | 266 | end 267 | 268 | local pEntity = VPhysicsGetEntity( pPhysObj ) 269 | 270 | if ( GetEntityTable( pEntity ).m_PhysHang ) then 271 | TryToRestore( pEntity ) 272 | else 273 | 274 | VPhysicsEnableMotion( pPhysObj, true ) 275 | VPhysicsWake( pPhysObj ) 276 | 277 | end 278 | 279 | Unfreezing.m_iCurrent = iCurrent + 1 280 | Unfreezing.m_iNextTime = flCurTime + DELAY 281 | 282 | net.Start( 'physcrashguard.Unfreeze' ) 283 | net.WriteUInt( UNFREEZE_PROGRESS, 2 ) 284 | net.WriteFloat( iCurrent / iTotal ) 285 | net.WriteEntity( pEntity ) 286 | net.Send( pl ) 287 | 288 | end 289 | 290 | end 291 | 292 | -- 293 | -- Set in use 294 | -- 295 | local ProcessGradualUnfreezing = physcrashguard.ProcessGradualUnfreezing 296 | 297 | hook.Add( 'PlayerPostThink', 'PhysicsCrashGuard_GradualUnfreezing', function( pl ) 298 | 299 | ProcessGradualUnfreezing( pl ) 300 | 301 | end ) 302 | -------------------------------------------------------------------------------- /physcrashguard/iterator.lua: -------------------------------------------------------------------------------- 1 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 2 | 3 | Iterator for all non-static physics objects that may potentially collide 4 | 5 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 6 | 7 | 8 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 9 | Prepare 10 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 11 | -- 12 | -- Metamethods: Entity; PhysObj 13 | -- 14 | local EntityMeta = FindMetaTable( 'Entity' ) 15 | 16 | local GetClass = EntityMeta.GetClass 17 | 18 | local IsWorld = EntityMeta.IsWorld 19 | 20 | local GetPhysicsObjectCount = EntityMeta.GetPhysicsObjectCount 21 | local GetPhysicsObjectNum = EntityMeta.GetPhysicsObjectNum 22 | 23 | local VPhysicsIsValid = FindMetaTable( 'PhysObj' ).IsValid 24 | 25 | -- 26 | -- Functions 27 | -- 28 | local EntitiesIterator = ents.Iterator 29 | 30 | local substrof = string.sub 31 | local tableinsert = table.insert 32 | 33 | -- 34 | -- Globals 35 | -- 36 | local physcrashguard = physcrashguard 37 | 38 | 39 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 40 | Cache for physics objects 41 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 42 | local PhysCache = {} 43 | 44 | local PHYSCACHE_SKIP = { 45 | 46 | prop_door_rotating = true; 47 | prop_dynamic = true 48 | 49 | } 50 | 51 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 52 | Purpose: Iterator for physics objects 53 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 54 | local subsequent = ipairs( {} ) 55 | 56 | function physcrashguard.Iterator() 57 | 58 | if ( PhysCache == nil ) then 59 | 60 | PhysCache = {} 61 | 62 | for _, pEntity in EntitiesIterator() do 63 | 64 | local strClass = GetClass( pEntity ) 65 | 66 | if ( PHYSCACHE_SKIP[strClass] or substrof( strClass, 1, 5 ) == 'func_' ) then 67 | continue 68 | end 69 | 70 | if ( physcrashguard.util.IsPlayer( pEntity ) or physcrashguard.util.IsNPC( pEntity ) or physcrashguard.util.IsVehicle( pEntity ) or IsWorld( pEntity ) ) then 71 | continue 72 | end 73 | 74 | local numPhysObjs = GetPhysicsObjectCount( pEntity ) 75 | 76 | for numObj = 1, numPhysObjs do 77 | 78 | local pPhysObj = GetPhysicsObjectNum( pEntity, numObj - 1 ) 79 | 80 | if ( VPhysicsIsValid( pPhysObj ) ) then 81 | tableinsert( PhysCache, pPhysObj ) 82 | end 83 | 84 | end 85 | 86 | end 87 | 88 | end 89 | 90 | return subsequent, PhysCache, 0 91 | 92 | end 93 | 94 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 95 | Purpose: Sets the cache up for update 96 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 97 | function physcrashguard.InvalidatePhysCache() 98 | 99 | PhysCache = nil 100 | 101 | end 102 | 103 | -- 104 | -- Set in use 105 | -- 106 | hook.Add( 'OnEntityCreated', 'PhysicsCrashGuard_Iterator', function( pEntity ) 107 | 108 | timer.Simple( 0, function() 109 | 110 | if ( pEntity:IsValid() ) then 111 | physcrashguard.InvalidatePhysCache() 112 | end 113 | 114 | end ) 115 | 116 | end ) 117 | 118 | hook.Add( 'EntityRemoved', 'PhysicsCrashGuard_Iterator', function( pEntity, bFullUpdate ) 119 | 120 | if ( bFullUpdate ) then 121 | return 122 | end 123 | 124 | physcrashguard.InvalidatePhysCache() 125 | 126 | end ) 127 | -------------------------------------------------------------------------------- /physcrashguard/main.lua: -------------------------------------------------------------------------------- 1 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 2 | 3 | Physics Crash Guard 4 | 5 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 6 | 7 | 8 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 9 | Init 10 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 11 | physcrashguard = physcrashguard or {} 12 | 13 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 14 | Purpose: Hang detection 15 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 16 | local physcrashguard_hangdetection = CreateConVar( 17 | 18 | 'physcrashguard_hangdetection', 19 | '14', 20 | 21 | SERVER and FCVAR_ARCHIVE or FCVAR_NONE, 22 | 23 | 'What delay (ms) in physics simulation will be considered as a physics hang', 24 | 0, 2000 25 | 26 | ) 27 | 28 | local g_flHangDetection = physcrashguard_hangdetection:GetFloat() 29 | 30 | cvars.AddChangeCallback( 'physcrashguard_hangdetection', function( _, _, value ) 31 | 32 | g_flHangDetection = ( tonumber( value ) or 14 ) / 1000 33 | 34 | end, 'Main' ) 35 | 36 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 37 | Purpose: Checks for physics hang 38 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 39 | local PhysEnvGetLastSimulationTime = physenv.GetLastSimulationTime 40 | 41 | function physcrashguard.IsThereHang() 42 | 43 | return PhysEnvGetLastSimulationTime() > g_flHangDetection 44 | 45 | end 46 | 47 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 48 | Assemble the addon 49 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 50 | if ( SERVER ) then 51 | 52 | include( 'util.lua' ) 53 | 54 | include( 'iterator.lua' ) 55 | 56 | include( 'resolving.lua' ) 57 | include( 'detection.lua' ) 58 | include( 'restoring.lua' ) 59 | 60 | include( 'constraints.lua' ) 61 | 62 | include( 'gradualunfreezing.lua' ) 63 | 64 | include( 'freezedupesonpaste.lua' ) 65 | 66 | AddCSLuaFile( 'client/gradualunfreezing.lua' ) 67 | AddCSLuaFile( 'client/settings.lua' ) 68 | 69 | elseif ( CLIENT ) then 70 | 71 | include( 'client/gradualunfreezing.lua' ) 72 | include( 'client/settings.lua' ) 73 | 74 | end 75 | -------------------------------------------------------------------------------- /physcrashguard/resolving.lua: -------------------------------------------------------------------------------- 1 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 2 | 3 | Resolver 4 | 5 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 6 | 7 | 8 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 9 | Prepare 10 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 11 | -- 12 | -- Metamethods: Entity; PhysObj 13 | -- 14 | local EntityMeta = FindMetaTable( 'Entity' ) 15 | 16 | local GetColor4Part = EntityMeta.GetColor4Part 17 | local SetColor4Part = EntityMeta.SetColor4Part 18 | 19 | local GetRenderMode = EntityMeta.GetRenderMode 20 | local SetRenderMode = EntityMeta.SetRenderMode 21 | 22 | local GetCollisionGroup = EntityMeta.GetCollisionGroup 23 | local SetCollisionGroup = EntityMeta.SetCollisionGroup 24 | 25 | local SetDrawShadow = EntityMeta.DrawShadow 26 | 27 | local Remove = EntityMeta.Remove 28 | 29 | local VPhysicsEnableMotion = FindMetaTable( 'PhysObj' ).EnableMotion 30 | 31 | -- 32 | -- Globals 33 | -- 34 | local RENDERMODE_TRANSCOLOR = RENDERMODE_TRANSCOLOR 35 | local COLLISION_GROUP_WORLD = COLLISION_GROUP_WORLD 36 | 37 | 38 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 39 | Purpose: Deletion mode 40 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 41 | local physcrashguard_delete = CreateConVar( 42 | 43 | 'physcrashguard_delete', 44 | '0', 45 | 46 | FCVAR_ARCHIVE, 47 | 48 | 'Experimental. Should entities to resolve be deleted? Won\'t apply to ragdolls.', 49 | 0, 1 50 | 51 | ) 52 | 53 | local g_bDeleteOnResolve = physcrashguard_delete:GetBool() 54 | 55 | cvars.AddChangeCallback( 'physcrashguard_delete', function( _, _, value ) 56 | 57 | g_bDeleteOnResolve = tobool( value ) 58 | 59 | end, 'Main' ) 60 | 61 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 62 | Resolve 63 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 64 | function physcrashguard.Resolve( pPhysObj, pEntity, pEntity_t ) 65 | 66 | if ( g_bDeleteOnResolve ) then 67 | 68 | Remove( pEntity ) 69 | return 70 | 71 | end 72 | 73 | if ( pEntity_t.m_PhysHang ) then 74 | return 75 | end 76 | 77 | local r, g, b, a = GetColor4Part( pEntity ) 78 | 79 | pEntity_t.m_PhysHang = { 80 | 81 | m_colLast = { r; g; b; a }; 82 | m_iLastRenderMode = GetRenderMode( pEntity ); 83 | m_iLastCollisionGroup = GetCollisionGroup( pEntity ) 84 | 85 | } 86 | 87 | VPhysicsEnableMotion( pPhysObj, false ) 88 | 89 | SetDrawShadow( pEntity, false ) 90 | 91 | SetRenderMode( pEntity, RENDERMODE_TRANSCOLOR ) 92 | SetColor4Part( pEntity, r, g, b, 180 ) 93 | 94 | SetCollisionGroup( pEntity, COLLISION_GROUP_WORLD ) 95 | 96 | end 97 | 98 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 99 | ResolveRagdoll 100 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 101 | function physcrashguard.ResolveRagdoll( pPhysPart, pRagdoll, pEntity_t ) 102 | 103 | if ( pEntity_t.m_PhysHang ) then 104 | 105 | local physparts = pEntity_t.m_PhysHang.m_PhysParts 106 | 107 | if ( not physparts[pPhysPart] ) then 108 | 109 | VPhysicsEnableMotion( pPhysPart, false ) 110 | 111 | local index = physparts[0] + 1 112 | 113 | physparts[index] = pPhysPart 114 | physparts[pPhysPart] = true 115 | 116 | physparts[0] = index 117 | 118 | end 119 | 120 | return 121 | 122 | end 123 | 124 | local r, g, b, a = GetColor4Part( pRagdoll ) 125 | 126 | pEntity_t.m_PhysHang = { 127 | 128 | m_colLast = { r; g; b; a }; 129 | m_iLastRenderMode = GetRenderMode( pRagdoll ); 130 | m_iLastCollisionGroup = GetCollisionGroup( pRagdoll ); 131 | 132 | m_PhysParts = { 133 | 134 | [0] = 1; 135 | 136 | [pPhysPart] = true; 137 | [1] = pPhysPart 138 | 139 | } 140 | 141 | } 142 | 143 | VPhysicsEnableMotion( pPhysPart, false ) 144 | 145 | SetDrawShadow( pRagdoll, false ) 146 | 147 | SetRenderMode( pRagdoll, RENDERMODE_TRANSCOLOR ) 148 | SetColor4Part( pRagdoll, r, g, b, 180 ) 149 | 150 | SetCollisionGroup( pRagdoll, COLLISION_GROUP_WORLD ) 151 | 152 | end 153 | -------------------------------------------------------------------------------- /physcrashguard/restoring.lua: -------------------------------------------------------------------------------- 1 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 2 | 3 | Restoring resolved objects 4 | 5 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 6 | 7 | 8 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 9 | Prepare 10 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 11 | -- 12 | -- Metamethods: Entity; PhysObj 13 | -- 14 | local EntityMeta = FindMetaTable( 'Entity' ) 15 | 16 | local SetCollisionGroup = EntityMeta.SetCollisionGroup 17 | local SetDrawShadow = EntityMeta.DrawShadow 18 | local SetColor4Part = EntityMeta.SetColor4Part 19 | local SetRenderMode = EntityMeta.SetRenderMode 20 | 21 | local GetEntityTable = EntityMeta.GetTable 22 | local GetPhysicsObject = EntityMeta.GetPhysicsObject 23 | 24 | local PhysObjMeta = FindMetaTable( 'PhysObj' ) 25 | 26 | local VPhysicsEnableMotion = PhysObjMeta.EnableMotion 27 | local VPhysicsWake = PhysObjMeta.Wake 28 | 29 | local VPhysicsIsPenetrating = PhysObjMeta.IsPenetrating 30 | 31 | -- 32 | -- Functions 33 | -- 34 | local unpack = unpack 35 | 36 | 37 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 38 | Restoring 39 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 40 | local function Restore( pEntity, pEntity_t, pPhysObj ) 41 | 42 | local PhysHang = pEntity_t.m_PhysHang 43 | local physparts = PhysHang.m_PhysParts 44 | 45 | if ( physparts ) then 46 | 47 | for i = 1, physparts[0] do 48 | 49 | local pPhysPart = physparts[i] 50 | 51 | VPhysicsEnableMotion( pPhysPart, true ) 52 | VPhysicsWake( pPhysPart ) 53 | 54 | end 55 | 56 | else 57 | 58 | VPhysicsEnableMotion( pPhysObj, true ) 59 | VPhysicsWake( pPhysObj ) 60 | 61 | end 62 | 63 | SetDrawShadow( pEntity, true ) 64 | 65 | SetColor4Part( pEntity, unpack( PhysHang.m_colLast, 1, 4 ) ) 66 | SetRenderMode( pEntity, PhysHang.m_iLastRenderMode ) 67 | 68 | SetCollisionGroup( pEntity, PhysHang.m_iLastCollisionGroup ) 69 | 70 | pEntity_t.m_PhysHang = nil 71 | 72 | end 73 | 74 | function physcrashguard.TryToRestore( pEntity ) 75 | 76 | local pEntity_t = GetEntityTable( pEntity ) 77 | local pPhysObj = GetPhysicsObject( pEntity ) 78 | 79 | if ( pEntity_t.m_PhysHang and not VPhysicsIsPenetrating( pPhysObj ) ) then 80 | Restore( pEntity, pEntity_t, pPhysObj ) 81 | end 82 | 83 | end 84 | 85 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 86 | Purpose: Try to restore 87 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 88 | hook.Add( 'OnPhysgunPickup', 'PhysicsCrashGuard_Restore', function( _, pEntity ) 89 | 90 | physcrashguard.TryToRestore( pEntity ) 91 | 92 | end ) 93 | 94 | hook.Add( 'GravGunOnPickedUp', 'PhysicsCrashGuard_Restore', function( _, pEntity ) 95 | 96 | physcrashguard.TryToRestore( pEntity ) 97 | 98 | end ) 99 | 100 | hook.Add( 'GravGunPunt', 'PhysicsCrashGuard_Restore', function( _, pEntity ) 101 | 102 | physcrashguard.TryToRestore( pEntity ) 103 | 104 | end ) 105 | -------------------------------------------------------------------------------- /physcrashguard/util.lua: -------------------------------------------------------------------------------- 1 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 2 | 3 | Utilities 4 | 5 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 6 | 7 | 8 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 9 | Prepare 10 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 11 | -- 12 | -- Shared Functions 13 | -- 14 | local getmetatable = getmetatable 15 | 16 | 17 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 18 | Init 19 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 20 | physcrashguard.util = physcrashguard.util or {} 21 | 22 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 23 | Purpose: Optimized entity-check 24 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 25 | local g_EntityMeta = FindMetaTable( 'Entity' ) 26 | 27 | function physcrashguard.util.IsEntity( any ) 28 | 29 | return getmetatable( any ) == g_EntityMeta 30 | 31 | end 32 | 33 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 34 | Purpose: Optimized vehicle-check 35 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 36 | local g_VehicleMeta = FindMetaTable( 'Vehicle' ) 37 | 38 | function physcrashguard.util.IsVehicle( any ) 39 | 40 | return getmetatable( any ) == g_VehicleMeta 41 | 42 | end 43 | 44 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 45 | Purpose: Optimized player-check 46 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 47 | local g_PlayerMeta = FindMetaTable( 'Player' ) 48 | 49 | function physcrashguard.util.IsPlayer( any ) 50 | 51 | return getmetatable( any ) == g_PlayerMeta 52 | 53 | end 54 | 55 | --[[––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 56 | Purpose: Optimized npc-check 57 | –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––]] 58 | local g_NPCMeta = FindMetaTable( 'NPC' ) 59 | 60 | function physcrashguard.util.IsNPC( any ) 61 | 62 | return getmetatable( any ) == g_NPCMeta 63 | 64 | end 65 | --------------------------------------------------------------------------------