@quenty/ragdoll 9.1.0 → 9.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,17 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [9.1.1](https://github.com/Quenty/NevermoreEngine/compare/@quenty/ragdoll@9.1.0...@quenty/ragdoll@9.1.1) (2022-10-16)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * **perf:** Make Ragdoll performance better ([566d2bb](https://github.com/Quenty/NevermoreEngine/commit/566d2bb1045d62fd08f16c9042d68c328c5c307d))
12
+
13
+
14
+
15
+
16
+
6
17
  # [9.1.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/ragdoll@9.0.0...@quenty/ragdoll@9.1.0) (2022-10-11)
7
18
 
8
19
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quenty/ragdoll",
3
- "version": "9.1.0",
3
+ "version": "9.1.1",
4
4
  "description": "Quenty's Ragdoll system for Roblox - Floppy fun ragdolls",
5
5
  "keywords": [
6
6
  "Roblox",
@@ -29,7 +29,7 @@
29
29
  "@quenty/baseobject": "^6.0.0",
30
30
  "@quenty/binder": "^8.1.0",
31
31
  "@quenty/brio": "^8.1.0",
32
- "@quenty/camera": "^9.1.0",
32
+ "@quenty/camera": "^9.1.1",
33
33
  "@quenty/cancellabledelay": "^3.3.0",
34
34
  "@quenty/characterutils": "^6.0.0",
35
35
  "@quenty/draw": "^4.2.0",
@@ -54,5 +54,5 @@
54
54
  "publishConfig": {
55
55
  "access": "public"
56
56
  },
57
- "gitHead": "69c2153865684748d0a8d6806f1ff999685c2e55"
57
+ "gitHead": "5fc8df6d4a62c0c774ebc78b275898f7f383935b"
58
58
  }
@@ -60,6 +60,7 @@ function RagdollClient:_setupCameraShake(impulseCamera)
60
60
 
61
61
  local lastVelocity = head.Velocity
62
62
  self._maid:GiveTask(RunService.Heartbeat:Connect(function()
63
+ debug.profilebegin("ragdollcamerashake")
63
64
  local cameraCFrame = Workspace.CurrentCamera.CFrame
64
65
 
65
66
  local velocity = head.Velocity
@@ -71,6 +72,7 @@ function RagdollClient:_setupCameraShake(impulseCamera)
71
72
  end
72
73
 
73
74
  lastVelocity = velocity
75
+ debug.profileend()
74
76
  end))
75
77
  end
76
78
 
@@ -15,6 +15,7 @@ local RagdollAdditionalAttachmentUtils = require("RagdollAdditionalAttachmentUti
15
15
  local RagdollCollisionUtils = require("RagdollCollisionUtils")
16
16
  local RagdollBallSocketUtils = require("RagdollBallSocketUtils")
17
17
  local Motor6DBindersServer = require("Motor6DBindersServer")
18
+ local RagdollMotorUtils = require("RagdollMotorUtils")
18
19
 
19
20
  local Ragdollable = setmetatable({}, BaseObject)
20
21
  Ragdollable.ClassName = "Ragdollable"
@@ -48,6 +49,9 @@ function Ragdollable.new(humanoid, serviceBag)
48
49
  maid:GiveTask(RagdollBallSocketUtils.ensureBallSockets(state.character, state.rigType))
49
50
  maid:GiveTask(RagdollCollisionUtils.ensureNoCollides(state.character, state.rigType))
50
51
 
52
+ -- Not super guarded against race conditions but it should be fine, as this is for debugging.
53
+ RagdollMotorUtils.initMotorAttributes(state.character, state.rigType)
54
+
51
55
  self._maid._configure = maid
52
56
  else
53
57
  self._maid._configure = nil
@@ -21,6 +21,10 @@ function RagdollService:Init(serviceBag)
21
21
 
22
22
  -- Internal
23
23
  self._binders = self._serviceBag:GetService(require("RagdollBindersServer"))
24
+
25
+ self._binders.RagdollHumanoidOnFall:SetAutomaticTagging(false)
26
+ self._binders.RagdollHumanoidOnDeath:SetAutomaticTagging(false)
27
+ self._binders.UnragdollAutomatically:SetAutomaticTagging(false)
24
28
  end
25
29
 
26
30
  --[=[
@@ -26,6 +26,9 @@ local Rx = require("Rx")
26
26
 
27
27
  local RagdollMotorUtils = {}
28
28
 
29
+ -- For easier debugging
30
+ local DEFAULT_SPRING_SPEED = 20
31
+
29
32
  local R6_MOTORS = {
30
33
  {
31
34
  partName = "Torso";
@@ -118,6 +121,23 @@ local R15_MOTORS = {
118
121
  };
119
122
  }
120
123
 
124
+ local ROOT_JOINT_CACHE = {}
125
+
126
+ function RagdollMotorUtils.getFirstRootJointData(rigType)
127
+ if ROOT_JOINT_CACHE[rigType] then
128
+ return ROOT_JOINT_CACHE[rigType]
129
+ end
130
+
131
+ for _, item in pairs(RagdollMotorUtils.getMotorData(rigType)) do
132
+ if item.isRootJoint then
133
+ ROOT_JOINT_CACHE[rigType] = item
134
+ return item
135
+ end
136
+ end
137
+
138
+ error("Could not find root joint data")
139
+ end
140
+
121
141
  function RagdollMotorUtils.getMotorData(rigType)
122
142
  if rigType == Enum.HumanoidRigType.R15 then
123
143
  return R15_MOTORS
@@ -128,158 +148,217 @@ function RagdollMotorUtils.getMotorData(rigType)
128
148
  end
129
149
  end
130
150
 
131
- function RagdollMotorUtils.suppressMotors(character, rigType, velocityReadings)
151
+ function RagdollMotorUtils.initMotorAttributes(character, rigType)
132
152
  assert(typeof(character) == "Instance" and character:IsA("Model"), "Bad character")
133
153
  assert(EnumUtils.isOfType(Enum.HumanoidRigType, rigType), "Bad rigType")
134
154
 
155
+ for _, data in pairs(RagdollMotorUtils.getMotorData(rigType)) do
156
+ local motor = R15Utils.getRigMotor(character, data.partName, data.motorName)
157
+ if motor then
158
+ AttributeUtils.initAttribute(motor, RagdollConstants.IS_MOTOR_ANIMATED_ATTRIBUTE, false)
159
+ AttributeUtils.initAttribute(motor, RagdollConstants.RETURN_SPRING_SPEED_ATTRIBUTE, DEFAULT_SPRING_SPEED)
160
+ end
161
+ end
162
+ end
163
+
164
+ function RagdollMotorUtils.setupAnimatedMotor(character, part)
165
+ local maid = Maid.new()
166
+
167
+ -- make this stuff not physics collide with our own rig
168
+ -- O(n^2)
169
+ maid:GiveTask(RagdollCollisionUtils.preventCollisionAmongOthers(character, part))
170
+
171
+ return maid
172
+ end
173
+
174
+ function RagdollMotorUtils.setupRagdollRootPartMotor(motor, part0, part1)
175
+ local maid = Maid.new()
176
+
177
+ local lastTransformSpring = Spring.new(QFrame.fromCFrameClosestTo(motor.Transform, QFrame.new()))
178
+ lastTransformSpring.t = QFrame.new()
179
+
180
+ -- replacing this weld ensures interpolation for some reason
181
+ local weldContainer = Instance.new("Camera")
182
+ weldContainer.Name = "TempWeldContainer"
183
+ weldContainer.Parent = part0
184
+ maid:GiveTask(weldContainer)
185
+
186
+ local function setupWeld(weldType)
187
+ local weldMaid = Maid.new()
188
+
189
+ local weld = Instance.new(weldType)
190
+ weld.Name = "TempRagdollWeld"
191
+ weld.Part0 = part0
192
+ weld.Part1 = part1
193
+ weldMaid:GiveTask(weld)
194
+
195
+ -- Inserted C1/C0 here
196
+ weldMaid:GiveTask(Rx.combineLatest({
197
+ C0 = RxInstanceUtils.observeProperty(motor, "C0");
198
+ Transform = RxInstanceUtils.observeProperty(motor, "Transform");
199
+ }):Subscribe(function(innerState)
200
+ weld.C0 = innerState.C0 * innerState.Transform
201
+ end))
202
+ weldMaid:GiveTask(RxInstanceUtils.observeProperty(motor, "C1"):Subscribe(function(c1)
203
+ weld.C1 = c1
204
+ end))
205
+ weld.Parent = weldContainer
206
+
207
+ return weldMaid
208
+ end
209
+
210
+ if CharacterUtils.getPlayerFromCharacter(part0) then
211
+ -- Swap from choppy to interpolation
212
+ maid._weld = setupWeld("Motor6D")
213
+ maid:GiveTask(task.delay(0.25, function()
214
+ maid._weld = setupWeld("Weld")
215
+ end))
216
+ else
217
+ -- Smooth all the way! (Probably NPC)
218
+ maid._weld = setupWeld("Weld")
219
+ end
220
+
221
+ maid:GiveTask(RxAttributeUtils.observeAttribute(motor, RagdollConstants.RETURN_SPRING_SPEED_ATTRIBUTE, DEFAULT_SPRING_SPEED)
222
+ :Subscribe(function(speed)
223
+ lastTransformSpring.s = speed
224
+ end))
225
+
226
+ -- Lerp smoothly to 0 to avoid jarring camera.
227
+ maid:GiveTask(RunService.Stepped:Connect(function()
228
+ local target = QFrame.toCFrame(lastTransformSpring.p)
229
+ if target then
230
+ motor.Transform = target
231
+ end
232
+ end))
233
+
234
+ motor.Enabled = false
235
+
236
+ maid:GiveTask(function()
237
+ motor.Enabled = true
238
+ end)
239
+
240
+ return maid
241
+ end
242
+
243
+ function RagdollMotorUtils.setupRagdollMotor(motor, part0, part1)
244
+ local maid = Maid.new()
245
+
246
+ motor.Enabled = false
247
+ maid:GiveTask(function()
248
+ local implemention = Motor6DStackInterface:FindFirstImplementation(motor)
249
+ if implemention then
250
+ local initialTransform = (part0.CFrame * motor.C0):toObjectSpace(part1.CFrame * motor.C1)
251
+ local speed = AttributeUtils.getAttribute(motor, RagdollConstants.RETURN_SPRING_SPEED_ATTRIBUTE, DEFAULT_SPRING_SPEED)
252
+
253
+ implemention:TransformFromCFrame(initialTransform, speed)
254
+ end
255
+
256
+ motor.Enabled = true
257
+ end)
258
+
259
+ maid:GiveTask(RunService.Stepped:Connect(function()
260
+ motor.Transform = CFrame.new()
261
+ end))
262
+
263
+ return maid
264
+ end
265
+
266
+ function RagdollMotorUtils.suppressJustRootPart(character, rigType)
267
+ local data = RagdollMotorUtils.getFirstRootJointData(rigType)
268
+
269
+ local observable = RxR15Utils.observeRigMotorBrio(character, data.partName, data.motorName):Pipe({
270
+ RxBrioUtils.switchMapBrio(function(motor)
271
+ return RxBrioUtils.flatCombineLatest({
272
+ motor = motor;
273
+ part0 = RxInstanceUtils.observeProperty(motor, "Part0");
274
+ part1 = RxInstanceUtils.observeProperty(motor, "Part1");
275
+ isAnimated = RxAttributeUtils.observeAttribute(motor, RagdollConstants.IS_MOTOR_ANIMATED_ATTRIBUTE, false);
276
+ })
277
+ end);
278
+ })
279
+
135
280
  local topMaid = Maid.new()
136
281
 
137
- for _, data in pairs(RagdollMotorUtils.getMotorData(rigType)) do
138
- local observable = RxR15Utils.observeRigMotorBrio(character, data.partName, data.motorName)
139
- topMaid:GiveTask(RxBrioUtils.flatCombineLatest({
140
- motor = observable;
141
- part0 = observable:Pipe({
142
- RxBrioUtils.switchMapBrio(function(motor)
143
- return RxInstanceUtils.observeProperty(motor, "Part0");
144
- end);
145
- });
146
- part1 = observable:Pipe({
147
- RxBrioUtils.switchMapBrio(function(motor)
148
- return RxInstanceUtils.observeProperty(motor, "Part1");
149
- end);
150
- });
151
- }):Subscribe(function(state)
152
- if state.motor and state.part0 and state.part1 then
153
- local motorMaid = Maid.new()
154
- local motor = state.motor
155
-
156
- -- For easier debugging
157
- local DEFAULT_SPEED = 20
158
- AttributeUtils.initAttribute(motor, RagdollConstants.IS_MOTOR_ANIMATED_ATTRIBUTE, false)
159
- AttributeUtils.initAttribute(motor, RagdollConstants.RETURN_SPRING_SPEED_ATTRIBUTE, DEFAULT_SPEED)
160
-
161
- motorMaid:GiveTask(RxAttributeUtils.observeAttribute(motor, RagdollConstants.IS_MOTOR_ANIMATED_ATTRIBUTE, false):Subscribe(function(isAnimated)
162
- if isAnimated then
163
- local lockMaid = Maid.new()
164
-
165
- -- make this stuff not physics collide with our own rig
166
- lockMaid:GiveTask(RagdollCollisionUtils.preventCollisionAmongOthers(character, state.part1))
167
-
168
- motorMaid._lock = lockMaid
169
- else
170
- local lockMaid = Maid.new()
171
-
172
- if data.isRootJoint then
173
- local lastTransformSpring = Spring.new(QFrame.fromCFrameClosestTo(motor.Transform, QFrame.new()))
174
- lastTransformSpring.t = QFrame.new()
175
-
176
- -- replacing this weld ensures interpolation for some reason
177
- local weldContainer = Instance.new("Camera")
178
- weldContainer.Name = "TempWeldContainer"
179
- weldContainer.Parent = state.part0
180
- lockMaid:GiveTask(weldContainer)
181
-
182
- local function setupWeld(weldType)
183
- local weldMaid = Maid.new()
184
-
185
- local weld = Instance.new(weldType)
186
- weld.Name = "TempRagdollWeld"
187
- weld.Part0 = state.part0
188
- weld.Part1 = state.part1
189
- weldMaid:GiveTask(weld)
190
-
191
- -- Inserted C1/C0 here
192
- weldMaid:GiveTask(Rx.combineLatest({
193
- C0 = RxInstanceUtils.observeProperty(motor, "C0");
194
- Transform = RxInstanceUtils.observeProperty(motor, "Transform");
195
- }):Subscribe(function(innerState)
196
- weld.C0 = innerState.C0 * innerState.Transform
197
- end))
198
- weldMaid:GiveTask(RxInstanceUtils.observeProperty(motor, "C1"):Subscribe(function(c1)
199
- weld.C1 = c1
200
- end))
201
- weld.Parent = weldContainer
202
-
203
- return weldMaid
204
- end
282
+ topMaid:GiveTask(observable:Subscribe(function(brio)
283
+ if brio:IsDead() then
284
+ return
285
+ end
205
286
 
287
+ local state = brio:GetValue()
288
+ local motorMaid = brio:ToMaid()
206
289
 
207
- if CharacterUtils.getPlayerFromCharacter(state.part0) then
208
- -- Swap from choppy to interpolation
209
- lockMaid._weld = setupWeld("Motor6D")
210
- lockMaid:GiveTask(task.delay(0.25, function()
211
- lockMaid._weld = setupWeld("Weld")
212
- end))
213
- else
214
- -- Smooth all the way! (Probably NPC)
215
- lockMaid._weld = setupWeld("Weld")
216
- end
290
+ if not (state.motor and state.part0 and state.part1) then
291
+ return
292
+ end
217
293
 
218
- lockMaid:GiveTask(RxAttributeUtils.observeAttribute(motor, RagdollConstants.RETURN_SPRING_SPEED_ATTRIBUTE, DEFAULT_SPEED)
219
- :Subscribe(function(speed)
220
- lastTransformSpring.s = speed
221
- end))
222
-
223
- -- Lerp smoothly to 0 to avoid jarring camera.
224
- lockMaid:GiveTask(RunService.Stepped:Connect(function()
225
- local target = QFrame.toCFrame(lastTransformSpring.p)
226
- if target then
227
- motor.Transform = target
228
- end
229
- end))
230
-
231
- motor.Enabled = false
232
-
233
- lockMaid:GiveTask(function()
234
- motor.Enabled = true
235
- end)
236
- else
237
- motor.Enabled = false
238
-
239
- lockMaid:GiveTask(function()
240
- local implemention = Motor6DStackInterface:FindFirstImplementation(state.motor)
241
- if implemention then
242
- local initialTransform = (state.part0.CFrame * motor.C0):toObjectSpace(state.part1.CFrame * motor.C1)
243
- local speed = AttributeUtils.getAttribute(state.motor, RagdollConstants.RETURN_SPRING_SPEED_ATTRIBUTE, DEFAULT_SPEED)
244
-
245
- implemention:TransformFromCFrame(initialTransform, speed)
246
- end
247
-
248
- motor.Enabled = true
249
- end)
250
-
251
- lockMaid:GiveTask(RunService.Stepped:Connect(function()
252
- motor.Transform = CFrame.new()
253
- end))
254
- end
294
+ if state.isAnimated then
295
+ motorMaid._current = RagdollMotorUtils.setupAnimatedMotor(character, state.part1)
296
+ else
297
+ motorMaid._current = RagdollMotorUtils.setupRagdollRootPartMotor(state.motor, state.part0, state.part1)
298
+ end
299
+ end))
255
300
 
256
- task.defer(function()
257
- -- Note animator:ApplyJointVelocities fails. Do this manually.
258
- -- We only want to do this on the network owner.
259
- if RagdollMotorUtils.guessIfNetworkOwner(state.part1) then
260
- -- use physics time
261
- local passed = time() - velocityReadings.readingTimePhysics
262
- if passed <= 0.1 then
263
- local rotVelocity = velocityReadings.rotation[data]
264
- if rotVelocity then
265
- state.part1.RotVelocity += rotVelocity
266
- end
267
-
268
- local velocity = velocityReadings.linear[data]
269
- if velocity then
270
- state.part1.Velocity += velocity
271
- end
272
- end
273
- end
274
- end)
301
+ return topMaid
302
+ end
303
+
304
+ function RagdollMotorUtils.suppressMotors(character, rigType, velocityReadings)
305
+ assert(typeof(character) == "Instance" and character:IsA("Model"), "Bad character")
306
+ assert(EnumUtils.isOfType(Enum.HumanoidRigType, rigType), "Bad rigType")
275
307
 
276
- motorMaid._lock = lockMaid
277
- end
278
- end))
308
+ local topMaid = Maid.new()
279
309
 
280
- topMaid[data] = motorMaid
310
+ for _, data in pairs(RagdollMotorUtils.getMotorData(rigType)) do
311
+ local observable = RxR15Utils.observeRigMotorBrio(character, data.partName, data.motorName):Pipe({
312
+ RxBrioUtils.switchMapBrio(function(motor)
313
+ return RxBrioUtils.flatCombineLatest({
314
+ motor = motor;
315
+ part0 = RxInstanceUtils.observeProperty(motor, "Part0");
316
+ part1 = RxInstanceUtils.observeProperty(motor, "Part1");
317
+ isAnimated = RxAttributeUtils.observeAttribute(motor, RagdollConstants.IS_MOTOR_ANIMATED_ATTRIBUTE, false);
318
+ })
319
+ end);
320
+ })
321
+
322
+ topMaid:GiveTask(observable:Subscribe(function(brio)
323
+ if brio:IsDead() then
324
+ return
325
+ end
326
+
327
+ local state = brio:GetValue()
328
+ local motorMaid = brio:ToMaid()
329
+
330
+ if not (state.motor and state.part0 and state.part1) then
331
+ return
332
+ end
333
+
334
+ if state.isAnimated then
335
+ motorMaid._current = RagdollMotorUtils.setupAnimatedMotor(character, state.part1)
281
336
  else
282
- topMaid[data] = nil
337
+ if data.isRootJoint then
338
+ motorMaid._current = RagdollMotorUtils.setupRagdollRootPartMotor(state.motor, state.part0, state.part1)
339
+ else
340
+ motorMaid._current = RagdollMotorUtils.setupRagdollMotor(state.motor, state.part0, state.part1)
341
+ end
342
+
343
+ -- Note animator:ApplyJointVelocities fails. Do this manually.
344
+ -- We only want to do this on the network owner.
345
+ if RagdollMotorUtils.guessIfNetworkOwner(state.part1) then
346
+ task.defer(function()
347
+ -- use physics time
348
+ local passed = time() - velocityReadings.readingTimePhysics
349
+ if passed <= 0.1 then
350
+ local rotVelocity = velocityReadings.rotation[data]
351
+ if rotVelocity then
352
+ state.part1.RotVelocity += rotVelocity
353
+ end
354
+
355
+ local velocity = velocityReadings.linear[data]
356
+ if velocity then
357
+ state.part1.Velocity += velocity
358
+ end
359
+ end
360
+ end)
361
+ end
283
362
  end
284
363
  end))
285
364
  end
@@ -6,11 +6,15 @@
6
6
 
7
7
  local require = require(script.Parent.loader).load(script)
8
8
 
9
+ local Players = game:GetService("Players")
10
+ local RunService = game:GetService("RunService")
11
+
9
12
  local Maid = require("Maid")
10
13
  local RxBrioUtils = require("RxBrioUtils")
11
14
  local RxInstanceUtils = require("RxInstanceUtils")
12
15
  local RxR15Utils = require("RxR15Utils")
13
16
  local RagdollMotorUtils = require("RagdollMotorUtils")
17
+ local CharacterUtils = require("CharacterUtils")
14
18
 
15
19
  local RxRagdollUtils = {}
16
20
 
@@ -94,19 +98,34 @@ function RxRagdollUtils.runLocal(humanoid)
94
98
 
95
99
  local maid = Maid.new()
96
100
 
97
- maid:GivePromise(RagdollMotorUtils.promiseVelocityRecordings(character, rigType))
98
- :Then(function(velocityReadings)
99
- maid:GiveTask(RxRagdollUtils.suppressRootPartCollision(character, rigType))
100
- maid:GiveTask(RxRagdollUtils.enforceHeadCollision(character))
101
+ local player = CharacterUtils.getPlayerFromCharacter(humanoid)
102
+ -- This velocity work only really needs to occur on the network owner and on the server
103
+ -- since the server will replicate all changes over to the client.
104
+ if RunService:IsServer() or player == Players.LocalPlayer then
105
+ maid:GivePromise(RagdollMotorUtils.promiseVelocityRecordings(character, rigType))
106
+ :Then(function(velocityReadings)
107
+ debug.profilebegin("initragdoll")
108
+
109
+ maid:GiveTask(RxRagdollUtils.suppressRootPartCollision(character, rigType))
110
+ maid:GiveTask(RxRagdollUtils.enforceHeadCollision(character))
111
+
112
+ -- Do motors
113
+ maid:GiveTask(RagdollMotorUtils.suppressMotors(character, rigType, velocityReadings))
101
114
 
102
- humanoid:ChangeState(Enum.HumanoidStateType.Physics)
103
- maid:GiveTask(function()
104
- humanoid:ChangeState(Enum.HumanoidStateType.GettingUp)
115
+ humanoid:ChangeState(Enum.HumanoidStateType.Physics)
116
+ maid:GiveTask(function()
117
+ humanoid:ChangeState(Enum.HumanoidStateType.GettingUp)
118
+ end)
119
+
120
+ debug.profileend()
105
121
  end)
122
+ else
123
+ debug.profilebegin("initragdoll_nonowner")
124
+
125
+ maid:GiveTask(RagdollMotorUtils.suppressJustRootPart(character, rigType))
106
126
 
107
- -- Do motors
108
- maid:GiveTask(RagdollMotorUtils.suppressMotors(character, rigType, velocityReadings))
109
- end)
127
+ debug.profileend()
128
+ end
110
129
 
111
130
  topMaid._current = maid
112
131
  else