@rbxts/gravity-controller 1.0.5 → 1.0.7
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/GravityController.rbxmx +156 -56
- package/README.md +324 -28
- package/out/index.d.ts +24 -0
- package/out/init.lua +225 -0
- package/package.json +1 -1
package/GravityController.rbxmx
CHANGED
|
@@ -3,13 +3,16 @@
|
|
|
3
3
|
<Properties>
|
|
4
4
|
<string name="Name">GravityController</string>
|
|
5
5
|
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
6
|
-
<
|
|
6
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
7
7
|
<bool name="Disabled">false</bool>
|
|
8
|
-
<
|
|
8
|
+
<UniqueId name="HistoryId">00000000000000000000000000000000</UniqueId>
|
|
9
|
+
<ContentId name="LinkedSource">
|
|
9
10
|
<null>
|
|
10
11
|
</null>
|
|
11
|
-
</
|
|
12
|
+
</ContentId>
|
|
12
13
|
<token name="RunContext">0</token>
|
|
14
|
+
<bool name="DefinesCapabilities">false</bool>
|
|
15
|
+
<string name="ScriptGuid">{eb65b9d8-9e71-469b-96da-3fd9967e0eea}</string>
|
|
13
16
|
<string name="Source">local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
|
14
17
|
local StarterPlayerScripts = game:GetService("StarterPlayer"):WaitForChild("StarterPlayerScripts")
|
|
15
18
|
local StarterCharacterScripts = game:GetService("StarterPlayer"):WaitForChild("StarterCharacterScripts")
|
|
@@ -29,39 +32,50 @@ replace(Client:WaitForChild("Animate"), StarterCharacterScripts)
|
|
|
29
32
|
script:WaitForChild("GravityController").Parent = ReplicatedStorage</string>
|
|
30
33
|
<int64 name="SourceAssetId">-1</int64>
|
|
31
34
|
<BinaryString name="Tags"></BinaryString>
|
|
35
|
+
<UniqueId name="UniqueId">62919a01c8dda09809ca8574000005d6</UniqueId>
|
|
32
36
|
</Properties>
|
|
33
37
|
<Item class="Folder" referent="1">
|
|
34
38
|
<Properties>
|
|
35
39
|
<string name="Name">Client</string>
|
|
36
40
|
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
41
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
42
|
+
<UniqueId name="HistoryId">00000000000000000000000000000000</UniqueId>
|
|
37
43
|
<bool name="DefinesCapabilities">false</bool>
|
|
38
44
|
<int64 name="SourceAssetId">-1</int64>
|
|
39
45
|
<BinaryString name="Tags"></BinaryString>
|
|
46
|
+
<UniqueId name="UniqueId">62919a01c8dda09809ca8574000005d7</UniqueId>
|
|
40
47
|
</Properties>
|
|
41
48
|
<Item class="LocalScript" referent="2">
|
|
42
49
|
<Properties>
|
|
43
50
|
<string name="Name">Animate</string>
|
|
44
51
|
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
45
|
-
<
|
|
52
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
46
53
|
<bool name="Disabled">false</bool>
|
|
47
|
-
<
|
|
54
|
+
<UniqueId name="HistoryId">00000000000000000000000000000000</UniqueId>
|
|
55
|
+
<ContentId name="LinkedSource">
|
|
48
56
|
<null>
|
|
49
57
|
</null>
|
|
50
|
-
</
|
|
58
|
+
</ContentId>
|
|
51
59
|
<token name="RunContext">0</token>
|
|
60
|
+
<bool name="DefinesCapabilities">false</bool>
|
|
61
|
+
<string name="ScriptGuid">{b0f949ba-48fa-4730-a891-99736e020579}</string>
|
|
52
62
|
<string name="Source">require(script:WaitForChild("Controller"))</string>
|
|
53
63
|
<int64 name="SourceAssetId">-1</int64>
|
|
54
64
|
<BinaryString name="Tags"></BinaryString>
|
|
65
|
+
<UniqueId name="UniqueId">62919a01c8dda09809ca8574000005d8</UniqueId>
|
|
55
66
|
</Properties>
|
|
56
67
|
<Item class="ModuleScript" referent="3">
|
|
57
68
|
<Properties>
|
|
58
69
|
<string name="Name">Controller</string>
|
|
59
70
|
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
60
|
-
<
|
|
61
|
-
<
|
|
71
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
72
|
+
<UniqueId name="HistoryId">00000000000000000000000000000000</UniqueId>
|
|
73
|
+
<ContentId name="LinkedSource">
|
|
62
74
|
<null>
|
|
63
75
|
</null>
|
|
64
|
-
</
|
|
76
|
+
</ContentId>
|
|
77
|
+
<bool name="DefinesCapabilities">false</bool>
|
|
78
|
+
<string name="ScriptGuid">{3d1c6cbf-6f7d-4e94-842b-2588e60c794e}</string>
|
|
65
79
|
<string name="Source">local animate = script.Parent
|
|
66
80
|
local humanoid = animate.Parent:WaitForChild("Humanoid")
|
|
67
81
|
local loaded = animate:WaitForChild("Loaded")
|
|
@@ -80,15 +94,19 @@ loaded.Value = true
|
|
|
80
94
|
return output</string>
|
|
81
95
|
<int64 name="SourceAssetId">-1</int64>
|
|
82
96
|
<BinaryString name="Tags"></BinaryString>
|
|
97
|
+
<UniqueId name="UniqueId">62919a01c8dda09809ca8574000005d9</UniqueId>
|
|
83
98
|
</Properties>
|
|
84
99
|
</Item>
|
|
85
100
|
<Item class="BoolValue" referent="4">
|
|
86
101
|
<Properties>
|
|
87
102
|
<string name="Name">Loaded</string>
|
|
88
103
|
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
104
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
105
|
+
<UniqueId name="HistoryId">00000000000000000000000000000000</UniqueId>
|
|
89
106
|
<bool name="DefinesCapabilities">false</bool>
|
|
90
107
|
<int64 name="SourceAssetId">-1</int64>
|
|
91
108
|
<BinaryString name="Tags"></BinaryString>
|
|
109
|
+
<UniqueId name="UniqueId">62919a01c8dda09809ca8574000005da</UniqueId>
|
|
92
110
|
<bool name="Value">false</bool>
|
|
93
111
|
</Properties>
|
|
94
112
|
</Item>
|
|
@@ -96,20 +114,26 @@ return output</string>
|
|
|
96
114
|
<Properties>
|
|
97
115
|
<string name="Name">PlayEmote</string>
|
|
98
116
|
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
117
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
118
|
+
<UniqueId name="HistoryId">00000000000000000000000000000000</UniqueId>
|
|
99
119
|
<bool name="DefinesCapabilities">false</bool>
|
|
100
120
|
<int64 name="SourceAssetId">-1</int64>
|
|
101
121
|
<BinaryString name="Tags"></BinaryString>
|
|
122
|
+
<UniqueId name="UniqueId">62919a01c8dda09809ca8574000005db</UniqueId>
|
|
102
123
|
</Properties>
|
|
103
124
|
</Item>
|
|
104
125
|
<Item class="ModuleScript" referent="6">
|
|
105
126
|
<Properties>
|
|
106
127
|
<string name="Name">R15</string>
|
|
107
128
|
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
108
|
-
<
|
|
109
|
-
<
|
|
129
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
130
|
+
<UniqueId name="HistoryId">00000000000000000000000000000000</UniqueId>
|
|
131
|
+
<ContentId name="LinkedSource">
|
|
110
132
|
<null>
|
|
111
133
|
</null>
|
|
112
|
-
</
|
|
134
|
+
</ContentId>
|
|
135
|
+
<bool name="DefinesCapabilities">false</bool>
|
|
136
|
+
<string name="ScriptGuid">{6754736d-045f-4c49-b9fb-cc82c0bfb1b5}</string>
|
|
113
137
|
<string name="Source"><![CDATA[local script = script.Parent
|
|
114
138
|
|
|
115
139
|
local Character = script.Parent
|
|
@@ -844,6 +868,7 @@ end
|
|
|
844
868
|
|
|
845
869
|
local function onHook()
|
|
846
870
|
onUnhook()
|
|
871
|
+
if not eventHum then return end
|
|
847
872
|
|
|
848
873
|
pose = Humanoid.Sit and "Seated" or "Standing"
|
|
849
874
|
|
|
@@ -867,6 +892,9 @@ RepHumanoid.Changed:Connect(function()
|
|
|
867
892
|
end)
|
|
868
893
|
|
|
869
894
|
RepHumanoid.Value = Humanoid
|
|
895
|
+
if #events == 0 then
|
|
896
|
+
onHook()
|
|
897
|
+
end
|
|
870
898
|
|
|
871
899
|
-- setup emote chat hook
|
|
872
900
|
game:GetService("Players").LocalPlayer.Chatted:connect(function(msg)
|
|
@@ -935,17 +963,21 @@ return {
|
|
|
935
963
|
]]></string>
|
|
936
964
|
<int64 name="SourceAssetId">-1</int64>
|
|
937
965
|
<BinaryString name="Tags"></BinaryString>
|
|
966
|
+
<UniqueId name="UniqueId">62919a01c8dda09809ca8574000005dc</UniqueId>
|
|
938
967
|
</Properties>
|
|
939
968
|
</Item>
|
|
940
969
|
<Item class="ModuleScript" referent="7">
|
|
941
970
|
<Properties>
|
|
942
971
|
<string name="Name">R6</string>
|
|
943
972
|
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
944
|
-
<
|
|
945
|
-
<
|
|
973
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
974
|
+
<UniqueId name="HistoryId">00000000000000000000000000000000</UniqueId>
|
|
975
|
+
<ContentId name="LinkedSource">
|
|
946
976
|
<null>
|
|
947
977
|
</null>
|
|
948
|
-
</
|
|
978
|
+
</ContentId>
|
|
979
|
+
<bool name="DefinesCapabilities">false</bool>
|
|
980
|
+
<string name="ScriptGuid">{7ab34f07-f6d8-4d43-a55b-e96b6488627a}</string>
|
|
949
981
|
<string name="Source">local script = script.Parent
|
|
950
982
|
|
|
951
983
|
local Figure = script.Parent
|
|
@@ -1458,6 +1490,7 @@ end
|
|
|
1458
1490
|
|
|
1459
1491
|
local function onHook()
|
|
1460
1492
|
onUnhook()
|
|
1493
|
+
if not eventHum then return end
|
|
1461
1494
|
|
|
1462
1495
|
pose = Humanoid.Sit and "Seated" or "Standing"
|
|
1463
1496
|
|
|
@@ -1481,6 +1514,9 @@ RepHumanoid.Changed:Connect(function()
|
|
|
1481
1514
|
end)
|
|
1482
1515
|
|
|
1483
1516
|
RepHumanoid.Value = Humanoid
|
|
1517
|
+
if #events == 0 then
|
|
1518
|
+
onHook()
|
|
1519
|
+
end
|
|
1484
1520
|
|
|
1485
1521
|
-- setup emote chat hook
|
|
1486
1522
|
game:GetService("Players").LocalPlayer.Chatted:connect(function(msg)
|
|
@@ -1528,15 +1564,19 @@ return {
|
|
|
1528
1564
|
}</string>
|
|
1529
1565
|
<int64 name="SourceAssetId">-1</int64>
|
|
1530
1566
|
<BinaryString name="Tags"></BinaryString>
|
|
1567
|
+
<UniqueId name="UniqueId">62919a01c8dda09809ca8574000005dd</UniqueId>
|
|
1531
1568
|
</Properties>
|
|
1532
1569
|
</Item>
|
|
1533
1570
|
<Item class="ObjectValue" referent="8">
|
|
1534
1571
|
<Properties>
|
|
1535
1572
|
<string name="Name">ReplicatedHumanoid</string>
|
|
1536
1573
|
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
1574
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
1575
|
+
<UniqueId name="HistoryId">00000000000000000000000000000000</UniqueId>
|
|
1537
1576
|
<bool name="DefinesCapabilities">false</bool>
|
|
1538
1577
|
<int64 name="SourceAssetId">-1</int64>
|
|
1539
1578
|
<BinaryString name="Tags"></BinaryString>
|
|
1579
|
+
<UniqueId name="UniqueId">62919a01c8dda09809ca8574000005de</UniqueId>
|
|
1540
1580
|
<Ref name="Value">null</Ref>
|
|
1541
1581
|
</Properties>
|
|
1542
1582
|
</Item>
|
|
@@ -1544,11 +1584,14 @@ return {
|
|
|
1544
1584
|
<Properties>
|
|
1545
1585
|
<string name="Name">VerifyAnims</string>
|
|
1546
1586
|
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
1547
|
-
<
|
|
1548
|
-
<
|
|
1587
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
1588
|
+
<UniqueId name="HistoryId">00000000000000000000000000000000</UniqueId>
|
|
1589
|
+
<ContentId name="LinkedSource">
|
|
1549
1590
|
<null>
|
|
1550
1591
|
</null>
|
|
1551
|
-
</
|
|
1592
|
+
</ContentId>
|
|
1593
|
+
<bool name="DefinesCapabilities">false</bool>
|
|
1594
|
+
<string name="ScriptGuid">{0ba70de6-b4f1-4e69-bd8b-61d26d449d79}</string>
|
|
1552
1595
|
<string name="Source">local LENGTH = string.len("Animation")
|
|
1553
1596
|
|
|
1554
1597
|
local DESC_ANIM_PROPS = {
|
|
@@ -1580,6 +1623,7 @@ return function(humanoid, animate)
|
|
|
1580
1623
|
end</string>
|
|
1581
1624
|
<int64 name="SourceAssetId">-1</int64>
|
|
1582
1625
|
<BinaryString name="Tags"></BinaryString>
|
|
1626
|
+
<UniqueId name="UniqueId">62919a01c8dda09809ca8574000005df</UniqueId>
|
|
1583
1627
|
</Properties>
|
|
1584
1628
|
</Item>
|
|
1585
1629
|
</Item>
|
|
@@ -1587,13 +1631,16 @@ end</string>
|
|
|
1587
1631
|
<Properties>
|
|
1588
1632
|
<string name="Name">PlayerScriptsLoader</string>
|
|
1589
1633
|
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
1590
|
-
<
|
|
1634
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
1591
1635
|
<bool name="Disabled">false</bool>
|
|
1592
|
-
<
|
|
1636
|
+
<UniqueId name="HistoryId">00000000000000000000000000000000</UniqueId>
|
|
1637
|
+
<ContentId name="LinkedSource">
|
|
1593
1638
|
<null>
|
|
1594
1639
|
</null>
|
|
1595
|
-
</
|
|
1640
|
+
</ContentId>
|
|
1596
1641
|
<token name="RunContext">0</token>
|
|
1642
|
+
<bool name="DefinesCapabilities">false</bool>
|
|
1643
|
+
<string name="ScriptGuid">{e0183d8c-3ce2-475d-ae7c-995a9c8941b8}</string>
|
|
1597
1644
|
<string name="Source">--[[
|
|
1598
1645
|
PlayerScriptsLoader - This script requires and instantiates the PlayerModule singleton
|
|
1599
1646
|
|
|
@@ -1697,7 +1744,7 @@ local defaultUpdateMouseBehavior = BaseCamera.UpdateMouseBehavior
|
|
|
1697
1744
|
|
|
1698
1745
|
function BaseCamera:UpdateMouseBehavior()
|
|
1699
1746
|
defaultUpdateMouseBehavior(self)
|
|
1700
|
-
if UserGameSettings.RotationType == Enum.RotationType.CameraRelative then
|
|
1747
|
+
if _G._gravityControllerActive and UserGameSettings.RotationType == Enum.RotationType.CameraRelative then
|
|
1701
1748
|
UserGameSettings.RotationType = Enum.RotationType.MovementRelative
|
|
1702
1749
|
end
|
|
1703
1750
|
end
|
|
@@ -1851,16 +1898,20 @@ end
|
|
|
1851
1898
|
require(PlayerModule)</string>
|
|
1852
1899
|
<int64 name="SourceAssetId">-1</int64>
|
|
1853
1900
|
<BinaryString name="Tags"></BinaryString>
|
|
1901
|
+
<UniqueId name="UniqueId">62919a01c8dda09809ca8574000005e0</UniqueId>
|
|
1854
1902
|
</Properties>
|
|
1855
1903
|
<Item class="ModuleScript" referent="11">
|
|
1856
1904
|
<Properties>
|
|
1857
1905
|
<string name="Name">CameraInjector</string>
|
|
1858
1906
|
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
1859
|
-
<
|
|
1860
|
-
<
|
|
1907
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
1908
|
+
<UniqueId name="HistoryId">00000000000000000000000000000000</UniqueId>
|
|
1909
|
+
<ContentId name="LinkedSource">
|
|
1861
1910
|
<null>
|
|
1862
1911
|
</null>
|
|
1863
|
-
</
|
|
1912
|
+
</ContentId>
|
|
1913
|
+
<bool name="DefinesCapabilities">false</bool>
|
|
1914
|
+
<string name="ScriptGuid">{431f5e99-73c4-410d-92cf-798558b9d76b}</string>
|
|
1864
1915
|
<string name="Source"><![CDATA[-- Injects into the CameraModule to override for public API access
|
|
1865
1916
|
-- EgoMoose
|
|
1866
1917
|
|
|
@@ -1936,17 +1987,21 @@ return result
|
|
|
1936
1987
|
]]></string>
|
|
1937
1988
|
<int64 name="SourceAssetId">-1</int64>
|
|
1938
1989
|
<BinaryString name="Tags"></BinaryString>
|
|
1990
|
+
<UniqueId name="UniqueId">62919a01c8dda09809ca8574000005e1</UniqueId>
|
|
1939
1991
|
</Properties>
|
|
1940
1992
|
</Item>
|
|
1941
1993
|
<Item class="ModuleScript" referent="12">
|
|
1942
1994
|
<Properties>
|
|
1943
1995
|
<string name="Name">FakeUserSettings</string>
|
|
1944
1996
|
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
1945
|
-
<
|
|
1946
|
-
<
|
|
1997
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
1998
|
+
<UniqueId name="HistoryId">00000000000000000000000000000000</UniqueId>
|
|
1999
|
+
<ContentId name="LinkedSource">
|
|
1947
2000
|
<null>
|
|
1948
2001
|
</null>
|
|
1949
|
-
</
|
|
2002
|
+
</ContentId>
|
|
2003
|
+
<bool name="DefinesCapabilities">false</bool>
|
|
2004
|
+
<string name="ScriptGuid">{eac70e9a-9f5c-452e-bae3-2826d0735557}</string>
|
|
1950
2005
|
<string name="Source">local FFLAG_OVERRIDES = {
|
|
1951
2006
|
["UserRemoveTheCameraApi"] = false
|
|
1952
2007
|
}
|
|
@@ -1978,6 +2033,7 @@ end
|
|
|
1978
2033
|
return FakeUserSettingsFunc</string>
|
|
1979
2034
|
<int64 name="SourceAssetId">-1</int64>
|
|
1980
2035
|
<BinaryString name="Tags"></BinaryString>
|
|
2036
|
+
<UniqueId name="UniqueId">62919a01c8dda09809ca8574000005e2</UniqueId>
|
|
1981
2037
|
</Properties>
|
|
1982
2038
|
</Item>
|
|
1983
2039
|
</Item>
|
|
@@ -1985,13 +2041,16 @@ return FakeUserSettingsFunc</string>
|
|
|
1985
2041
|
<Properties>
|
|
1986
2042
|
<string name="Name">RbxCharacterSounds</string>
|
|
1987
2043
|
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
1988
|
-
<
|
|
2044
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
1989
2045
|
<bool name="Disabled">false</bool>
|
|
1990
|
-
<
|
|
2046
|
+
<UniqueId name="HistoryId">00000000000000000000000000000000</UniqueId>
|
|
2047
|
+
<ContentId name="LinkedSource">
|
|
1991
2048
|
<null>
|
|
1992
2049
|
</null>
|
|
1993
|
-
</
|
|
2050
|
+
</ContentId>
|
|
1994
2051
|
<token name="RunContext">0</token>
|
|
2052
|
+
<bool name="DefinesCapabilities">false</bool>
|
|
2053
|
+
<string name="ScriptGuid">{cdad40e6-3a62-4b8c-b94e-2dd8d206ffdb}</string>
|
|
1995
2054
|
<string name="Source"><![CDATA[-- Roblox character sound script
|
|
1996
2055
|
|
|
1997
2056
|
local Players = game:GetService("Players")
|
|
@@ -2316,16 +2375,20 @@ end
|
|
|
2316
2375
|
]]></string>
|
|
2317
2376
|
<int64 name="SourceAssetId">-1</int64>
|
|
2318
2377
|
<BinaryString name="Tags"></BinaryString>
|
|
2378
|
+
<UniqueId name="UniqueId">62919a01c8dda09809ca8574000005e3</UniqueId>
|
|
2319
2379
|
</Properties>
|
|
2320
2380
|
<Item class="ModuleScript" referent="14">
|
|
2321
2381
|
<Properties>
|
|
2322
2382
|
<string name="Name">AnimationState</string>
|
|
2323
2383
|
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
2324
|
-
<
|
|
2325
|
-
<
|
|
2384
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
2385
|
+
<UniqueId name="HistoryId">00000000000000000000000000000000</UniqueId>
|
|
2386
|
+
<ContentId name="LinkedSource">
|
|
2326
2387
|
<null>
|
|
2327
2388
|
</null>
|
|
2328
|
-
</
|
|
2389
|
+
</ContentId>
|
|
2390
|
+
<bool name="DefinesCapabilities">false</bool>
|
|
2391
|
+
<string name="ScriptGuid">{9fdc64e9-3148-4289-9ab9-51fe1358b8d9}</string>
|
|
2329
2392
|
<string name="Source">local STATE_MAP = {
|
|
2330
2393
|
["climb"] = Enum.HumanoidStateType.Climbing,
|
|
2331
2394
|
["fall"] = Enum.HumanoidStateType.Freefall,
|
|
@@ -2357,6 +2420,7 @@ return function(animator, callback)
|
|
|
2357
2420
|
end</string>
|
|
2358
2421
|
<int64 name="SourceAssetId">-1</int64>
|
|
2359
2422
|
<BinaryString name="Tags"></BinaryString>
|
|
2423
|
+
<UniqueId name="UniqueId">62919a01c8dda09809ca8574000005e4</UniqueId>
|
|
2360
2424
|
</Properties>
|
|
2361
2425
|
</Item>
|
|
2362
2426
|
</Item>
|
|
@@ -2365,11 +2429,14 @@ end</string>
|
|
|
2365
2429
|
<Properties>
|
|
2366
2430
|
<string name="Name">GravityController</string>
|
|
2367
2431
|
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
2368
|
-
<
|
|
2369
|
-
<
|
|
2432
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
2433
|
+
<UniqueId name="HistoryId">00000000000000000000000000000000</UniqueId>
|
|
2434
|
+
<ContentId name="LinkedSource">
|
|
2370
2435
|
<null>
|
|
2371
2436
|
</null>
|
|
2372
|
-
</
|
|
2437
|
+
</ContentId>
|
|
2438
|
+
<bool name="DefinesCapabilities">false</bool>
|
|
2439
|
+
<string name="ScriptGuid">{f6e2e306-cb25-41c8-91a6-74b8518edd95}</string>
|
|
2373
2440
|
<string name="Source">local RunService = game:GetService("RunService")
|
|
2374
2441
|
|
|
2375
2442
|
local Utility = script:WaitForChild("Utility")
|
|
@@ -2595,6 +2662,7 @@ function init(self)
|
|
|
2595
2662
|
self._characterMass = getModelMass(self.Character)
|
|
2596
2663
|
end))
|
|
2597
2664
|
|
|
2665
|
+
_G._gravityControllerActive = true
|
|
2598
2666
|
self.Humanoid.PlatformStand = true
|
|
2599
2667
|
self.Maid:Mark(self.Humanoid:GetPropertyChangedSignal("Jump"):Connect(function()
|
|
2600
2668
|
if self.Humanoid.Jump then
|
|
@@ -2652,6 +2720,7 @@ function GravityControllerClass:Destroy()
|
|
|
2652
2720
|
RunService:UnbindFromRenderStep("GravityStep")
|
|
2653
2721
|
self.Maid:Sweep()
|
|
2654
2722
|
self.Humanoid.PlatformStand = false
|
|
2723
|
+
_G._gravityControllerActive = false
|
|
2655
2724
|
end
|
|
2656
2725
|
|
|
2657
2726
|
--
|
|
@@ -2659,24 +2728,31 @@ end
|
|
|
2659
2728
|
return GravityControllerClass</string>
|
|
2660
2729
|
<int64 name="SourceAssetId">-1</int64>
|
|
2661
2730
|
<BinaryString name="Tags"></BinaryString>
|
|
2731
|
+
<UniqueId name="UniqueId">62919a01c8dda09809ca8574000005e5</UniqueId>
|
|
2662
2732
|
</Properties>
|
|
2663
2733
|
<Item class="Folder" referent="16">
|
|
2664
2734
|
<Properties>
|
|
2665
2735
|
<string name="Name">CharacterModules</string>
|
|
2666
2736
|
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
2737
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
2738
|
+
<UniqueId name="HistoryId">00000000000000000000000000000000</UniqueId>
|
|
2667
2739
|
<bool name="DefinesCapabilities">false</bool>
|
|
2668
2740
|
<int64 name="SourceAssetId">-1</int64>
|
|
2669
2741
|
<BinaryString name="Tags"></BinaryString>
|
|
2742
|
+
<UniqueId name="UniqueId">62919a01c8dda09809ca8574000005e6</UniqueId>
|
|
2670
2743
|
</Properties>
|
|
2671
2744
|
<Item class="ModuleScript" referent="17">
|
|
2672
2745
|
<Properties>
|
|
2673
2746
|
<string name="Name">Camera</string>
|
|
2674
2747
|
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
2675
|
-
<
|
|
2676
|
-
<
|
|
2748
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
2749
|
+
<UniqueId name="HistoryId">00000000000000000000000000000000</UniqueId>
|
|
2750
|
+
<ContentId name="LinkedSource">
|
|
2677
2751
|
<null>
|
|
2678
2752
|
</null>
|
|
2679
|
-
</
|
|
2753
|
+
</ContentId>
|
|
2754
|
+
<bool name="DefinesCapabilities">false</bool>
|
|
2755
|
+
<string name="ScriptGuid">{76d10358-03a7-4411-9c24-5a674dd6c931}</string>
|
|
2680
2756
|
<string name="Source">-- Class
|
|
2681
2757
|
|
|
2682
2758
|
local CameraClass = {}
|
|
@@ -2723,17 +2799,21 @@ end
|
|
|
2723
2799
|
return CameraClass</string>
|
|
2724
2800
|
<int64 name="SourceAssetId">-1</int64>
|
|
2725
2801
|
<BinaryString name="Tags"></BinaryString>
|
|
2802
|
+
<UniqueId name="UniqueId">62919a01c8dda09809ca8574000005e7</UniqueId>
|
|
2726
2803
|
</Properties>
|
|
2727
2804
|
</Item>
|
|
2728
2805
|
<Item class="ModuleScript" referent="18">
|
|
2729
2806
|
<Properties>
|
|
2730
2807
|
<string name="Name">Control</string>
|
|
2731
2808
|
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
2732
|
-
<
|
|
2733
|
-
<
|
|
2809
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
2810
|
+
<UniqueId name="HistoryId">00000000000000000000000000000000</UniqueId>
|
|
2811
|
+
<ContentId name="LinkedSource">
|
|
2734
2812
|
<null>
|
|
2735
2813
|
</null>
|
|
2736
|
-
</
|
|
2814
|
+
</ContentId>
|
|
2815
|
+
<bool name="DefinesCapabilities">false</bool>
|
|
2816
|
+
<string name="ScriptGuid">{10647ef5-c281-4a40-b0bb-8038409c7a2d}</string>
|
|
2737
2817
|
<string name="Source">-- Class
|
|
2738
2818
|
|
|
2739
2819
|
local ControlClass = {}
|
|
@@ -2771,6 +2851,7 @@ end
|
|
|
2771
2851
|
return ControlClass</string>
|
|
2772
2852
|
<int64 name="SourceAssetId">-1</int64>
|
|
2773
2853
|
<BinaryString name="Tags"></BinaryString>
|
|
2854
|
+
<UniqueId name="UniqueId">62919a01c8dda09809ca8574000005e8</UniqueId>
|
|
2774
2855
|
</Properties>
|
|
2775
2856
|
</Item>
|
|
2776
2857
|
</Item>
|
|
@@ -2778,11 +2859,14 @@ return ControlClass</string>
|
|
|
2778
2859
|
<Properties>
|
|
2779
2860
|
<string name="Name">Collider</string>
|
|
2780
2861
|
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
2781
|
-
<
|
|
2782
|
-
<
|
|
2862
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
2863
|
+
<UniqueId name="HistoryId">00000000000000000000000000000000</UniqueId>
|
|
2864
|
+
<ContentId name="LinkedSource">
|
|
2783
2865
|
<null>
|
|
2784
2866
|
</null>
|
|
2785
|
-
</
|
|
2867
|
+
</ContentId>
|
|
2868
|
+
<bool name="DefinesCapabilities">false</bool>
|
|
2869
|
+
<string name="ScriptGuid">{f0cc7fc1-6d6d-4df8-a71d-31e31a485f35}</string>
|
|
2786
2870
|
<string name="Source"><![CDATA[local Maid = require(script.Parent.Utility.Maid)
|
|
2787
2871
|
|
|
2788
2872
|
local params = RaycastParams.new()
|
|
@@ -3019,17 +3103,21 @@ return ColliderClass
|
|
|
3019
3103
|
]]></string>
|
|
3020
3104
|
<int64 name="SourceAssetId">-1</int64>
|
|
3021
3105
|
<BinaryString name="Tags"></BinaryString>
|
|
3106
|
+
<UniqueId name="UniqueId">62919a01c8dda09809ca8574000005e9</UniqueId>
|
|
3022
3107
|
</Properties>
|
|
3023
3108
|
</Item>
|
|
3024
3109
|
<Item class="ModuleScript" referent="20">
|
|
3025
3110
|
<Properties>
|
|
3026
3111
|
<string name="Name">StateTracker</string>
|
|
3027
3112
|
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
3028
|
-
<
|
|
3029
|
-
<
|
|
3113
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
3114
|
+
<UniqueId name="HistoryId">00000000000000000000000000000000</UniqueId>
|
|
3115
|
+
<ContentId name="LinkedSource">
|
|
3030
3116
|
<null>
|
|
3031
3117
|
</null>
|
|
3032
|
-
</
|
|
3118
|
+
</ContentId>
|
|
3119
|
+
<bool name="DefinesCapabilities">false</bool>
|
|
3120
|
+
<string name="ScriptGuid">{7ebcb610-6d6d-492e-9422-94acd49b969d}</string>
|
|
3033
3121
|
<string name="Source">local Maid = require(script.Parent.Utility.Maid)
|
|
3034
3122
|
local Signal = require(script.Parent.Utility.Signal)
|
|
3035
3123
|
|
|
@@ -3145,25 +3233,32 @@ end
|
|
|
3145
3233
|
return StateTrackerClass</string>
|
|
3146
3234
|
<int64 name="SourceAssetId">-1</int64>
|
|
3147
3235
|
<BinaryString name="Tags"></BinaryString>
|
|
3236
|
+
<UniqueId name="UniqueId">62919a01c8dda09809ca8574000005ea</UniqueId>
|
|
3148
3237
|
</Properties>
|
|
3149
3238
|
</Item>
|
|
3150
3239
|
<Item class="Folder" referent="21">
|
|
3151
3240
|
<Properties>
|
|
3152
3241
|
<string name="Name">Utility</string>
|
|
3153
3242
|
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
3243
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
3244
|
+
<UniqueId name="HistoryId">00000000000000000000000000000000</UniqueId>
|
|
3154
3245
|
<bool name="DefinesCapabilities">false</bool>
|
|
3155
3246
|
<int64 name="SourceAssetId">-1</int64>
|
|
3156
3247
|
<BinaryString name="Tags"></BinaryString>
|
|
3248
|
+
<UniqueId name="UniqueId">62919a01c8dda09809ca8574000005eb</UniqueId>
|
|
3157
3249
|
</Properties>
|
|
3158
3250
|
<Item class="ModuleScript" referent="22">
|
|
3159
3251
|
<Properties>
|
|
3160
3252
|
<string name="Name">Maid</string>
|
|
3161
3253
|
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
3162
|
-
<
|
|
3163
|
-
<
|
|
3254
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
3255
|
+
<UniqueId name="HistoryId">00000000000000000000000000000000</UniqueId>
|
|
3256
|
+
<ContentId name="LinkedSource">
|
|
3164
3257
|
<null>
|
|
3165
3258
|
</null>
|
|
3166
|
-
</
|
|
3259
|
+
</ContentId>
|
|
3260
|
+
<bool name="DefinesCapabilities">false</bool>
|
|
3261
|
+
<string name="ScriptGuid">{a0996765-17f7-4168-a2cf-13d8501d57f7}</string>
|
|
3167
3262
|
<string name="Source">-- CONSTANTS
|
|
3168
3263
|
|
|
3169
3264
|
local FORMAT_STR = "Maid does not support type \"%s\""
|
|
@@ -3237,17 +3332,21 @@ MaidClass.Destroy = MaidClass.Sweep
|
|
|
3237
3332
|
return MaidClass</string>
|
|
3238
3333
|
<int64 name="SourceAssetId">-1</int64>
|
|
3239
3334
|
<BinaryString name="Tags"></BinaryString>
|
|
3335
|
+
<UniqueId name="UniqueId">62919a01c8dda09809ca8574000005ec</UniqueId>
|
|
3240
3336
|
</Properties>
|
|
3241
3337
|
</Item>
|
|
3242
3338
|
<Item class="ModuleScript" referent="23">
|
|
3243
3339
|
<Properties>
|
|
3244
3340
|
<string name="Name">Signal</string>
|
|
3245
3341
|
<BinaryString name="AttributesSerialize"></BinaryString>
|
|
3246
|
-
<
|
|
3247
|
-
<
|
|
3342
|
+
<SecurityCapabilities name="Capabilities">0</SecurityCapabilities>
|
|
3343
|
+
<UniqueId name="HistoryId">00000000000000000000000000000000</UniqueId>
|
|
3344
|
+
<ContentId name="LinkedSource">
|
|
3248
3345
|
<null>
|
|
3249
3346
|
</null>
|
|
3250
|
-
</
|
|
3347
|
+
</ContentId>
|
|
3348
|
+
<bool name="DefinesCapabilities">false</bool>
|
|
3349
|
+
<string name="ScriptGuid">{8108a59e-e9e8-499c-8311-78d8a49a0a92}</string>
|
|
3251
3350
|
<string name="Source">-- Taken from Quenty's nevermore engine
|
|
3252
3351
|
-- https://github.com/Quenty/NevermoreEngine/blob/version2/LICENSE.md
|
|
3253
3352
|
-- https://github.com/Quenty/NevermoreEngine
|
|
@@ -3335,9 +3434,10 @@ end
|
|
|
3335
3434
|
return Signal</string>
|
|
3336
3435
|
<int64 name="SourceAssetId">-1</int64>
|
|
3337
3436
|
<BinaryString name="Tags"></BinaryString>
|
|
3437
|
+
<UniqueId name="UniqueId">62919a01c8dda09809ca8574000005ed</UniqueId>
|
|
3338
3438
|
</Properties>
|
|
3339
3439
|
</Item>
|
|
3340
3440
|
</Item>
|
|
3341
3441
|
</Item>
|
|
3342
3442
|
</Item>
|
|
3343
|
-
</roblox>
|
|
3443
|
+
</roblox>
|
package/README.md
CHANGED
|
@@ -1,11 +1,273 @@
|
|
|
1
1
|
# @rbxts/gravity-controller
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
ground
|
|
3
|
+
TypeScript bindings for [EgoMoose's Rbx-Gravity-Controller](https://github.com/EgoMoose/Rbx-Gravity-Controller) with
|
|
4
|
+
ground-normal-based wall walking by [EmilyBendsSpace](https://x.com/EmilyBendsSpace).
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Players can walk on walls, ceilings, and any arbitrary surface with smooth gravity transitions.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @rbxts/gravity-controller
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The package ships a `GravityController.rbxmx` model that contains the Lua runtime (camera, collider, state tracker, animations, and character sounds). The TypeScript wrapper in `src/index.ts` handles deploying those scripts at runtime and provides typed access to the controller.
|
|
15
|
+
|
|
16
|
+
## How it works
|
|
17
|
+
|
|
18
|
+
### Architecture
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
Server (onInit) Client (onStart)
|
|
22
|
+
───────────────── ──────────────────
|
|
23
|
+
installGravityControllerClass() installGravityControllerClass()
|
|
24
|
+
├─ Copies Client/PlayerScriptsLoader └─ require("GravityController")
|
|
25
|
+
│ → StarterPlayerScripts from ReplicatedStorage
|
|
26
|
+
├─ Copies Client/RbxCharacterSounds ↓
|
|
27
|
+
│ → StarterPlayerScripts new GravityControllerClass(player)
|
|
28
|
+
├─ Copies Client/Animate ├─ Camera (custom camera module)
|
|
29
|
+
│ → StarterCharacterScripts ├─ Control (input → move vector)
|
|
30
|
+
└─ Moves GravityController module ├─ Collider (physics body movers)
|
|
31
|
+
→ ReplicatedStorage └─ StateTracker (humanoid states)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`installGravityControllerClass()` must be called on **both** the server and the client. On the server it deploys the bundled scripts into `StarterPlayerScripts`, `StarterCharacterScripts`, and `ReplicatedStorage`. On the client it `require`s the `GravityController` ModuleScript from `ReplicatedStorage` and returns the class.
|
|
35
|
+
|
|
36
|
+
### Permanent runtime modifications
|
|
37
|
+
|
|
38
|
+
The server-side `installGravityControllerClass()` **replaces** scripts in StarterPlayerScripts / StarterCharacterScripts:
|
|
39
|
+
|
|
40
|
+
- **PlayerScriptsLoader** — modified version that monkey-patches `BaseCamera`, `CameraUtils`, `Poppercam`, and the `CameraModule.Update` loop to support arbitrary gravity up-vectors
|
|
41
|
+
- **Animate** — custom version that works with `PlatformStand = true`
|
|
42
|
+
- **RbxCharacterSounds** — custom version driven by the gravity controller's `StateTracker` rather than native `Humanoid` state
|
|
43
|
+
|
|
44
|
+
These replacements are **permanent for the session** — they survive `GravityController:Destroy()`.
|
|
45
|
+
|
|
46
|
+
### Key monkey-patch: `BaseCamera:UpdateMouseBehavior()`
|
|
47
|
+
|
|
48
|
+
The modified PlayerScriptsLoader overrides `BaseCamera:UpdateMouseBehavior()` to force `UserGameSettings.RotationType` from `CameraRelative` to `MovementRelative`. This is necessary while the gravity controller is active because `CameraRelative` rotation conflicts with the custom `BodyGyro`-driven character orientation.
|
|
49
|
+
|
|
50
|
+
This override is guarded by `_G._gravityControllerActive` so it only applies while a gravity controller instance is alive. Without this guard, any first-person camera system that depends on `CameraRelative` (such as Character-Realism's `FpsCamera`) will break permanently after gravity controller destruction.
|
|
51
|
+
|
|
52
|
+
### What a GravityController instance does (per-player, temporary)
|
|
53
|
+
|
|
54
|
+
- Sets `Humanoid.PlatformStand = true` (disables the default humanoid physics)
|
|
55
|
+
- Creates collision proxy parts (Sphere, FloorDetector, JumpDetector) welded to HRP
|
|
56
|
+
- Adds `VectorForce`, `BodyGyro`, `BodyPosition` to HRP
|
|
57
|
+
- Binds `"GravityStep"` RenderStep at priority `Camera - 1` (199)
|
|
58
|
+
- Sets `_G._gravityControllerActive = true`
|
|
59
|
+
|
|
60
|
+
### What `GravityController:Destroy()` does
|
|
61
|
+
|
|
62
|
+
- Unbinds `"GravityStep"` from RenderStep
|
|
63
|
+
- `Maid:Sweep()` — destroys all proxy parts, body movers, disconnects events
|
|
64
|
+
- Sets `Humanoid.PlatformStand = false`
|
|
65
|
+
- Sets `_G._gravityControllerActive = false`
|
|
66
|
+
|
|
67
|
+
It does **not** restore the Animate script or the PlayerScriptsLoader — those persist.
|
|
68
|
+
|
|
69
|
+
### Gravity step (per frame)
|
|
70
|
+
|
|
71
|
+
Each render frame the controller runs `onGravityStep`:
|
|
72
|
+
|
|
73
|
+
1. **Query gravity direction** — calls `GetGravityUp(oldGravity)` which you can override. By default it returns the previous gravity (no change). Assign `getGravityControllerUp` to enable surface-following wall walk.
|
|
74
|
+
2. **Lerp transition** — spherically interpolates from the old gravity direction toward the new one, controlled by `Transition` (default `0.15`).
|
|
75
|
+
3. **Compute world move vector** — projects the camera-relative input onto the plane perpendicular to gravity so the character always moves along the surface.
|
|
76
|
+
4. **Compute forces** — calculates a counter-gravity force (`gForce`) and a walk force (`walkForce`) that accelerates the character toward target velocity.
|
|
77
|
+
5. **Optional horizontal lock** — when `UseBodyPositionLock` is enabled and the character is standing still on an aligned surface, a `BodyPosition` prevents micro-sliding.
|
|
78
|
+
6. **Update collider and state** — applies the combined force to the character's body movers and updates the state tracker (running, jumping, freefall, etc.).
|
|
79
|
+
|
|
80
|
+
### Interaction with first-person camera systems
|
|
81
|
+
|
|
82
|
+
If you use a first-person camera module (e.g. Character-Realism's `FpsCamera`) that depends on `UserGameSettings.RotationType` being `CameraRelative`:
|
|
83
|
+
|
|
84
|
+
- While the gravity controller is **active**, `onGravityStep` handles character rotation via `BodyGyro`. The `_G._gravityControllerActive` flag ensures the monkey-patch forces `MovementRelative` only during this time.
|
|
85
|
+
- After `Destroy()`, the flag clears and the monkey-patch becomes a no-op, allowing your first-person system to set `AutoRotate = false` and take over character rotation normally.
|
|
86
|
+
|
|
87
|
+
### Ground normal detection (`getGroundNormal`)
|
|
88
|
+
|
|
89
|
+
The exported `getGroundNormal` function determines which direction is "up" by casting rays from the character's root part:
|
|
90
|
+
|
|
91
|
+
| Ray group | Count | Purpose |
|
|
92
|
+
|---|---|---|
|
|
93
|
+
| Center ray | 1 | Single downward ray (length 25) to find the surface directly below |
|
|
94
|
+
| Down rays | 24 | Radial ring of rays angled slightly inward/outward, with alternating even/odd radii, to sample the surrounding surface normals |
|
|
95
|
+
| Feeler rays | 9 | Shorter rays (length 2) fanning outward and downward to detect walls and edges the character is approaching |
|
|
96
|
+
|
|
97
|
+
All hit normals are weighted (front-facing rays weighted more heavily, feelers weighted 8x) and summed. The final unit vector becomes the new "up" direction. If no rays hit anything, the previous gravity direction is preserved.
|
|
98
|
+
|
|
99
|
+
### `GravityController.rbxmx` file manifest
|
|
100
|
+
|
|
101
|
+
The `.rbxmx` model bundles all the Lua scripts needed at runtime. During `installGravityControllerClass()` these are deployed to the correct locations in the Roblox data model.
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
GravityController (Script) ← server entry point; deploys children at runtime
|
|
105
|
+
├── Client (Folder) ← scripts that get copied into StarterPlayer
|
|
106
|
+
│ ├── Animate (LocalScript) → StarterCharacterScripts
|
|
107
|
+
│ │ ├── Controller (ModuleScript) — bootstraps R6/R15 animation sets
|
|
108
|
+
│ │ ├── Loaded (BoolValue) — signals when animations are ready
|
|
109
|
+
│ │ ├── PlayEmote (BindableFunction) — emote playback hook
|
|
110
|
+
│ │ ├── R15 (ModuleScript) — full R15 animation state machine
|
|
111
|
+
│ │ ├── R6 (ModuleScript) — full R6 animation state machine
|
|
112
|
+
│ │ ├── ReplicatedHumanoid (ObjectValue) — humanoid reference for replication
|
|
113
|
+
│ │ └── VerifyAnims (ModuleScript) — validates animation assets on the character
|
|
114
|
+
│ ├── PlayerScriptsLoader (LocalScript) → StarterPlayerScripts
|
|
115
|
+
│ │ ├── CameraInjector (ModuleScript) — monkey-patches PlayerModule's CameraModule
|
|
116
|
+
│ │ │ to expose a public GetUpVector API for gravity-aware camera rotation
|
|
117
|
+
│ │ └── FakeUserSettings (ModuleScript) — shims UserSettings() to override feature
|
|
118
|
+
│ │ flags (e.g. disables UserRemoveTheCameraApi) during camera injection
|
|
119
|
+
│ └── RbxCharacterSounds (LocalScript) → StarterPlayerScripts
|
|
120
|
+
│ └── AnimationState (ModuleScript) — maps animation track names to
|
|
121
|
+
│ HumanoidStateTypes so footstep/jump/fall sounds play correctly
|
|
122
|
+
│ under custom gravity
|
|
123
|
+
└── GravityController (ModuleScript) → ReplicatedStorage
|
|
124
|
+
├── CharacterModules (Folder)
|
|
125
|
+
│ ├── Camera (ModuleScript) — hooks into PlayerModule cameras to override
|
|
126
|
+
│ │ GetUpVector, making the camera orbit around the custom gravity axis
|
|
127
|
+
│ └── Control (ModuleScript) — wraps PlayerModule controls to read the
|
|
128
|
+
│ move vector from keyboard/gamepad/touch input
|
|
129
|
+
├── Collider (ModuleScript) — creates an invisible Ball Part welded below
|
|
130
|
+
│ the HRP for ground detection, plus VectorForce (gravity + walk),
|
|
131
|
+
│ BodyGyro (orientation), and BodyPosition (optional anti-slide lock)
|
|
132
|
+
├── StateTracker (ModuleScript) — replaces Humanoid state detection with
|
|
133
|
+
│ velocity-based Running/Jumping/Freefall tracking and fires the
|
|
134
|
+
│ Animate script's callbacks (onRunning, onJumping, onFreeFall, etc.)
|
|
135
|
+
└── Utility (Folder)
|
|
136
|
+
├── Maid (ModuleScript) — connection/instance cleanup utility
|
|
137
|
+
└── Signal (ModuleScript) — lightweight event/signal implementation
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Where each piece ends up at runtime:**
|
|
141
|
+
|
|
142
|
+
| Script | Deployed to | Role |
|
|
143
|
+
|---|---|---|
|
|
144
|
+
| `Client/PlayerScriptsLoader` | `StarterPlayerScripts` | Replaces the default PlayerScriptsLoader to inject gravity-aware camera and control overrides into the stock `PlayerModule` |
|
|
145
|
+
| `Client/RbxCharacterSounds` | `StarterPlayerScripts` | Replaces default character sounds so audio triggers are driven by the custom `StateTracker` instead of the native `Humanoid` state |
|
|
146
|
+
| `Client/Animate` | `StarterCharacterScripts` | Replaces default Animate script; plays R6/R15 animations driven by `StateTracker.Changed` events rather than native humanoid states |
|
|
147
|
+
| `GravityController` | `ReplicatedStorage` | The core module — `require`d by both the TypeScript wrapper and the PlayerScriptsLoader at runtime |
|
|
148
|
+
|
|
149
|
+
## API
|
|
150
|
+
|
|
151
|
+
### `installGravityControllerClass(config?)`
|
|
152
|
+
|
|
153
|
+
Initializes the gravity system. Call on both server and client. Returns the `GravityControllerClass` constructor. Idempotent — calling it again returns the same class (and optionally applies new config).
|
|
154
|
+
|
|
155
|
+
### `GravityControllerClass`
|
|
156
|
+
|
|
157
|
+
| Member | Type | Description |
|
|
158
|
+
|---|---|---|
|
|
159
|
+
| `new(player)` | constructor | Creates a controller for the given player's current character |
|
|
160
|
+
| `SetConstants(config)` | static method | Updates physics constants globally (see Configuration below) |
|
|
161
|
+
|
|
162
|
+
### `GravityController` (instance)
|
|
163
|
+
|
|
164
|
+
| Member | Type | Description |
|
|
165
|
+
|---|---|---|
|
|
166
|
+
| `Player` | `Player` | The owning player |
|
|
167
|
+
| `Character` | `Model` | The player's character model |
|
|
168
|
+
| `Humanoid` | `Humanoid` | The character's humanoid |
|
|
169
|
+
| `HRP` | `BasePart` | `HumanoidRootPart` |
|
|
170
|
+
| `Maid` | `{ Mark }` | Cleanup helper — tracks connections for automatic teardown |
|
|
171
|
+
| `GetGravityUp(oldGravity)` | method | Override this to control gravity direction each frame. Default returns `oldGravity` (no change). |
|
|
172
|
+
| `ResetGravity(direction)` | method | Instantly sets the gravity-up vector and resets the fall tracker |
|
|
173
|
+
| `GetFallHeight()` | method | Returns the signed distance fallen along the gravity axis while in freefall; `0` otherwise |
|
|
174
|
+
| `Destroy()` | method | Unbinds the render step, sweeps all connections, and restores `PlatformStand` |
|
|
175
|
+
|
|
176
|
+
### `GravityManager`
|
|
177
|
+
|
|
178
|
+
A plain TypeScript class (no framework dependencies) that manages the full lifecycle of a `GravityController` instance — waiting for the character and Animate script, constructing the controller, handling timeouts and retries, and tearing down cleanly.
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
const cls = installGravityControllerClass(config)
|
|
182
|
+
const manager = new GravityManager(cls, logger)
|
|
183
|
+
|
|
184
|
+
manager.enable(getGravityControllerUp) // async 4-phase construction
|
|
185
|
+
manager.disable() // teardown + reset GravityUp attribute
|
|
186
|
+
manager.getController() // live instance or undefined
|
|
187
|
+
manager.getIsEnabling() // true while construction is in-flight
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**Constructor:** `new GravityManager(gravityControllerClass, logger?)`
|
|
191
|
+
|
|
192
|
+
- `gravityControllerClass` — the class returned by `installGravityControllerClass()`
|
|
193
|
+
- `logger` — optional `GravityLogger` (see below). If omitted, logging is silently skipped.
|
|
194
|
+
|
|
195
|
+
**`enable(getGravityUp)`** — starts a 4-phase async construction:
|
|
196
|
+
|
|
197
|
+
1. Waits for `Players.LocalPlayer.Character` to exist
|
|
198
|
+
2. Waits for the `Animate` script and its `Controller` child (with timeout)
|
|
199
|
+
3. Waits for the `Animate` module to finish loading (`Loaded.Value = true`)
|
|
200
|
+
4. Constructs the `GravityController` instance and assigns `GetGravityUp`
|
|
201
|
+
|
|
202
|
+
Each call stores the requested `getGravityUp` function. If `enable()` is called again while construction is in-flight, the new function is saved and will be used when the current construction completes or on retry.
|
|
203
|
+
|
|
204
|
+
A generation counter invalidates stale constructions — if `disable()` is called while construction is running, the in-flight thread is cancelled and any completed-but-stale controller is destroyed immediately.
|
|
205
|
+
|
|
206
|
+
A watchdog fires after the timeout period and hard-cancels stuck construction threads, cleans up partial state (`GravityStep` RenderStep binding, `PlatformStand`), and triggers a retry.
|
|
207
|
+
|
|
208
|
+
**`disable()`** — tears down the active controller:
|
|
209
|
+
|
|
210
|
+
- Increments the generation counter (invalidating any in-flight construction)
|
|
211
|
+
- Cancels the construction thread if running
|
|
212
|
+
- Calls `Destroy()` on the live controller
|
|
213
|
+
- Sets the HRP `GravityUp` attribute to `(0, 1, 0)`
|
|
214
|
+
|
|
215
|
+
**`getController()`** — returns the live `GravityController` instance or `undefined`.
|
|
216
|
+
|
|
217
|
+
**`getIsEnabling()`** — returns `true` while construction is in-flight.
|
|
218
|
+
|
|
219
|
+
### `GravityLogger`
|
|
220
|
+
|
|
221
|
+
Minimal logging interface so the package doesn't depend on any specific logging library. Any object with `Info`, `Warn`, and `Error` string methods satisfies it — including `@rbxts/log`'s `Logger`.
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
interface GravityLogger {
|
|
225
|
+
Info(message: string): void
|
|
226
|
+
Warn(message: string): void
|
|
227
|
+
Error(message: string): void
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### `wrapGravityUpSaveAttribute(getGravityUp)`
|
|
232
|
+
|
|
233
|
+
Higher-order function that wraps a `GetGravityUp` function to persist the current gravity direction as an HRP attribute (`GravityUp`). Only writes when the direction actually changes (magnitude delta > 0.001).
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
manager.enable(wrapGravityUpSaveAttribute(getGravityControllerUp))
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### `GetGravityUp` (type)
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
type GetGravityUp = (self: GravityController, oldGravityUp: Vector3) => Vector3
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
The signature for gravity direction functions. Passed to `GravityManager.enable()` or assigned to `controller.GetGravityUp`.
|
|
246
|
+
|
|
247
|
+
### `getGravityControllerUp(controller, oldGravityUp)`
|
|
248
|
+
|
|
249
|
+
Convenience wrapper that calls `getGroundNormal` using the controller's `HRP.CFrame` with a rig-type-aware origin offset. Assign this to `controller.GetGravityUp` to enable wall walking.
|
|
250
|
+
|
|
251
|
+
### `getGroundNormal(cframe, originOffset, oldGravityUp)`
|
|
252
|
+
|
|
253
|
+
Low-level raycast function that returns a unit `Vector3` representing the surface normal beneath and around `cframe`. Useful if you want to build your own gravity logic.
|
|
254
|
+
|
|
255
|
+
## Configuration
|
|
256
|
+
|
|
257
|
+
Pass a config table to `installGravityControllerClass()` or call `SetConstants()` at any time:
|
|
258
|
+
|
|
259
|
+
| Key | Type | Default | Description |
|
|
260
|
+
|---|---|---|---|
|
|
261
|
+
| `Transition` | `number` | `0.15` | Lerp alpha per frame for gravity direction changes. Lower = slower, smoother transitions. |
|
|
262
|
+
| `WalkForce` | `number` | `66.67` | Horizontal acceleration multiplier. Increase for snappier movement. |
|
|
263
|
+
| `JumpModifier` | `number` | `1.2` | Multiplier on `Humanoid.JumpPower` when jumping along the custom gravity axis. |
|
|
264
|
+
| `UseBodyPositionLock` | `boolean` | `false` | When `true`, locks the character's horizontal position with a `BodyPosition` while idle on an aligned surface to prevent sliding. |
|
|
265
|
+
|
|
266
|
+
## Usage
|
|
267
|
+
|
|
268
|
+
### With GravityManager (recommended)
|
|
269
|
+
|
|
270
|
+
**Server** — install in a `@Service`:
|
|
9
271
|
|
|
10
272
|
```typescript
|
|
11
273
|
import { OnInit, Service } from '@flamework/core'
|
|
@@ -14,52 +276,86 @@ import { installGravityControllerClass } from '@rbxts/gravity-controller'
|
|
|
14
276
|
@Service()
|
|
15
277
|
export class GravityService implements OnInit {
|
|
16
278
|
onInit() {
|
|
17
|
-
// Install EgoMoose's Rbx-Gravity-Controller https://github.com/EgoMoose/Rbx-Gravity-Controller
|
|
18
279
|
installGravityControllerClass()
|
|
19
280
|
}
|
|
20
281
|
}
|
|
21
282
|
```
|
|
22
283
|
|
|
23
|
-
|
|
284
|
+
**Client** — use `GravityManager` to handle the full enable/disable lifecycle:
|
|
24
285
|
|
|
25
286
|
```typescript
|
|
26
287
|
import { Controller, OnStart } from '@flamework/core'
|
|
27
288
|
import {
|
|
28
289
|
getGravityControllerUp,
|
|
29
|
-
|
|
30
|
-
GravityControllerClass,
|
|
290
|
+
GravityManager,
|
|
31
291
|
installGravityControllerClass,
|
|
292
|
+
wrapGravityUpSaveAttribute,
|
|
32
293
|
} from '@rbxts/gravity-controller'
|
|
33
|
-
import {
|
|
294
|
+
import { Logger } from '@rbxts/log'
|
|
34
295
|
|
|
35
296
|
@Controller({})
|
|
36
297
|
export class PlayerGravityController implements OnStart {
|
|
37
|
-
|
|
38
|
-
|
|
298
|
+
private gravityManager: GravityManager | undefined
|
|
299
|
+
|
|
300
|
+
constructor(private logger: Logger) {}
|
|
39
301
|
|
|
40
302
|
onStart() {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
303
|
+
const cls = installGravityControllerClass()
|
|
304
|
+
this.gravityManager = new GravityManager(cls, this.logger)
|
|
305
|
+
|
|
306
|
+
// Enable gravity with surface-following wall walk
|
|
307
|
+
this.gravityManager.enable(
|
|
308
|
+
wrapGravityUpSaveAttribute(getGravityControllerUp),
|
|
309
|
+
)
|
|
47
310
|
}
|
|
48
311
|
|
|
49
|
-
|
|
50
|
-
this.
|
|
51
|
-
this.gravityController = undefined
|
|
312
|
+
disable() {
|
|
313
|
+
this.gravityManager?.disable()
|
|
52
314
|
}
|
|
315
|
+
}
|
|
316
|
+
```
|
|
53
317
|
|
|
54
|
-
|
|
55
|
-
if (this.gravityController || !this.gravityControllerClass) return
|
|
56
|
-
const gravityController = new this.gravityControllerClass(Players.LocalPlayer)
|
|
318
|
+
### Manual lifecycle (without GravityManager)
|
|
57
319
|
|
|
58
|
-
|
|
59
|
-
gravityController.GetGravityUp = getGravityControllerUp
|
|
320
|
+
If you need full control over construction timing:
|
|
60
321
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
322
|
+
```typescript
|
|
323
|
+
import {
|
|
324
|
+
installGravityControllerClass,
|
|
325
|
+
getGravityControllerUp,
|
|
326
|
+
} from '@rbxts/gravity-controller'
|
|
327
|
+
import { Players } from '@rbxts/services'
|
|
328
|
+
|
|
329
|
+
const GravityControllerClass = installGravityControllerClass()
|
|
64
330
|
|
|
331
|
+
Players.LocalPlayer.CharacterAdded.Connect(() => {
|
|
332
|
+
const gc = new GravityControllerClass(Players.LocalPlayer)
|
|
333
|
+
gc.GetGravityUp = getGravityControllerUp
|
|
334
|
+
})
|
|
65
335
|
```
|
|
336
|
+
|
|
337
|
+
### Custom gravity direction
|
|
338
|
+
|
|
339
|
+
If you don't want surface-following wall walk, you can point gravity in any fixed direction:
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
const gc = new GravityControllerClass(Players.LocalPlayer)
|
|
343
|
+
|
|
344
|
+
// Gravity pulls toward -X (sideways)
|
|
345
|
+
gc.GetGravityUp = () => new Vector3(1, 0, 0)
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
Or reset gravity imperatively:
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
gc.ResetGravity(new Vector3(0, -1, 0)) // flip upside down
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
## Credits
|
|
355
|
+
|
|
356
|
+
- [EgoMoose](https://github.com/EgoMoose) — original [Rbx-Gravity-Controller](https://github.com/EgoMoose/Rbx-Gravity-Controller) Lua implementation
|
|
357
|
+
- [EmilyBendsSpace](https://x.com/EmilyBendsSpace) — improved ground normal raycasting for smooth wall walking ([DevForum post](https://devforum.roblox.com/t/example-source-smooth-wall-walking-gravity-controller-from-club-raven/440229))
|
|
358
|
+
|
|
359
|
+
## License
|
|
360
|
+
|
|
361
|
+
MIT
|
package/out/index.d.ts
CHANGED
|
@@ -28,7 +28,31 @@ export interface GravityControllerConfig {
|
|
|
28
28
|
JumpModifier?: number;
|
|
29
29
|
UseBodyPositionLock?: boolean;
|
|
30
30
|
}
|
|
31
|
+
export interface GravityLogger {
|
|
32
|
+
Info(message: string): void;
|
|
33
|
+
Warn(message: string): void;
|
|
34
|
+
Error(message: string): void;
|
|
35
|
+
}
|
|
36
|
+
export type GetGravityUp = (self: GravityController, oldGravityUp: Vector3) => Vector3;
|
|
31
37
|
export declare let gravityControllerClass: GravityControllerClass;
|
|
32
38
|
export declare function installGravityControllerClass(config?: GravityControllerConfig): GravityControllerClass;
|
|
39
|
+
export declare function wrapGravityUpSaveAttribute(getGravityUp: GetGravityUp): (gravityController: GravityController, oldGravityUp: Vector3) => Vector3;
|
|
40
|
+
export declare class GravityManager {
|
|
41
|
+
private _controller;
|
|
42
|
+
private _enabling;
|
|
43
|
+
private enablingStartedAt;
|
|
44
|
+
private generation;
|
|
45
|
+
private retryCount;
|
|
46
|
+
private constructionThread;
|
|
47
|
+
private pendingGetGravityUp;
|
|
48
|
+
private readonly gravityControllerClass;
|
|
49
|
+
private readonly logger;
|
|
50
|
+
constructor(gravityControllerClass: GravityControllerClass, logger?: GravityLogger);
|
|
51
|
+
getController(): GravityController | undefined;
|
|
52
|
+
getIsEnabling(): boolean;
|
|
53
|
+
disable(): void;
|
|
54
|
+
enable(getGravityUp: GetGravityUp): void;
|
|
55
|
+
private retryEnable;
|
|
56
|
+
}
|
|
33
57
|
export declare function getGroundNormal(cframe: CFrame, originOffset: Vector3, oldGravityUp: Vector3): Vector3;
|
|
34
58
|
export declare function getGravityControllerUp(gravityController: GravityController, oldGravityUp: Vector3): Vector3;
|
package/out/init.lua
CHANGED
|
@@ -6,6 +6,7 @@ local Players = _services.Players
|
|
|
6
6
|
local ReplicatedStorage = _services.ReplicatedStorage
|
|
7
7
|
local RunService = _services.RunService
|
|
8
8
|
local StarterPlayer = _services.StarterPlayer
|
|
9
|
+
local Workspace = _services.Workspace
|
|
9
10
|
local function installGravityControllerClass(config)
|
|
10
11
|
if exports.gravityControllerClass then
|
|
11
12
|
if config then
|
|
@@ -44,6 +45,228 @@ local function installGravityControllerClass(config)
|
|
|
44
45
|
end
|
|
45
46
|
return exports.gravityControllerClass
|
|
46
47
|
end
|
|
48
|
+
-- ── Utility ──────────────────────────────────────────────────────────
|
|
49
|
+
local function wrapGravityUpSaveAttribute(getGravityUp)
|
|
50
|
+
return function(gravityController, oldGravityUp)
|
|
51
|
+
local up = getGravityUp(gravityController, oldGravityUp)
|
|
52
|
+
local _oldGravityUp = oldGravityUp
|
|
53
|
+
if (up - _oldGravityUp).Magnitude > 0.001 then
|
|
54
|
+
gravityController.HRP:SetAttribute("GravityUp", up)
|
|
55
|
+
end
|
|
56
|
+
return up
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
-- ── GravityManager ───────────────────────────────────────────────────
|
|
60
|
+
local ENABLING_STALE_SEC = 5
|
|
61
|
+
local MAX_GRAVITY_RETRIES = 3
|
|
62
|
+
local RETRY_BACKOFF_SEC = 2
|
|
63
|
+
local noopLogger = {
|
|
64
|
+
Info = function(self) end,
|
|
65
|
+
Warn = function(self) end,
|
|
66
|
+
Error = function(self) end,
|
|
67
|
+
}
|
|
68
|
+
local GravityManager
|
|
69
|
+
do
|
|
70
|
+
GravityManager = setmetatable({}, {
|
|
71
|
+
__tostring = function()
|
|
72
|
+
return "GravityManager"
|
|
73
|
+
end,
|
|
74
|
+
})
|
|
75
|
+
GravityManager.__index = GravityManager
|
|
76
|
+
function GravityManager.new(...)
|
|
77
|
+
local self = setmetatable({}, GravityManager)
|
|
78
|
+
return self:constructor(...) or self
|
|
79
|
+
end
|
|
80
|
+
function GravityManager:constructor(gravityControllerClass, logger)
|
|
81
|
+
self._enabling = false
|
|
82
|
+
self.enablingStartedAt = 0
|
|
83
|
+
self.generation = 0
|
|
84
|
+
self.retryCount = 0
|
|
85
|
+
self.gravityControllerClass = gravityControllerClass
|
|
86
|
+
self.logger = logger or noopLogger
|
|
87
|
+
end
|
|
88
|
+
function GravityManager:getController()
|
|
89
|
+
return self._controller
|
|
90
|
+
end
|
|
91
|
+
function GravityManager:getIsEnabling()
|
|
92
|
+
return self._enabling
|
|
93
|
+
end
|
|
94
|
+
function GravityManager:disable()
|
|
95
|
+
local wasActive = self._controller ~= nil
|
|
96
|
+
local wasEnabling = self._enabling
|
|
97
|
+
if not wasActive and not wasEnabling then
|
|
98
|
+
return nil
|
|
99
|
+
end
|
|
100
|
+
local prevGen = self.generation
|
|
101
|
+
self.generation += 1
|
|
102
|
+
self._enabling = false
|
|
103
|
+
self.pendingGetGravityUp = nil
|
|
104
|
+
self.retryCount = 0
|
|
105
|
+
if self.constructionThread then
|
|
106
|
+
pcall(function()
|
|
107
|
+
return task.cancel(self.constructionThread)
|
|
108
|
+
end)
|
|
109
|
+
self.constructionThread = nil
|
|
110
|
+
end
|
|
111
|
+
self.logger:Info(`Disabling gravity controller: wasActive={wasActive}, wasEnabling={wasEnabling}` .. `, gen={prevGen}->{self.generation}`)
|
|
112
|
+
local _result = self._controller
|
|
113
|
+
if _result ~= nil then
|
|
114
|
+
_result:Destroy()
|
|
115
|
+
end
|
|
116
|
+
self._controller = nil
|
|
117
|
+
local _result_1 = Players.LocalPlayer.Character
|
|
118
|
+
if _result_1 ~= nil then
|
|
119
|
+
_result_1 = _result_1:FindFirstChild("HumanoidRootPart")
|
|
120
|
+
end
|
|
121
|
+
local hrp = _result_1
|
|
122
|
+
local _result_2 = hrp
|
|
123
|
+
if _result_2 ~= nil then
|
|
124
|
+
_result_2:SetAttribute("GravityUp", Vector3.new(0, 1, 0))
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
function GravityManager:enable(getGravityUp)
|
|
128
|
+
self.pendingGetGravityUp = getGravityUp
|
|
129
|
+
if self._controller then
|
|
130
|
+
return nil
|
|
131
|
+
end
|
|
132
|
+
local now = Workspace:GetServerTimeNow()
|
|
133
|
+
if self._enabling and now - self.enablingStartedAt < ENABLING_STALE_SEC then
|
|
134
|
+
return nil
|
|
135
|
+
end
|
|
136
|
+
self._enabling = true
|
|
137
|
+
self.enablingStartedAt = now
|
|
138
|
+
local generation = self.generation
|
|
139
|
+
local timeout = ENABLING_STALE_SEC + self.retryCount * RETRY_BACKOFF_SEC
|
|
140
|
+
self.logger:Info(`Enabling gravity controller (gen {generation}, timeout {timeout}s)`)
|
|
141
|
+
self.constructionThread = task.spawn(function()
|
|
142
|
+
-- Phase 1: wait for character
|
|
143
|
+
if not Players.LocalPlayer.Character then
|
|
144
|
+
Players.LocalPlayer.CharacterAdded:Wait()
|
|
145
|
+
end
|
|
146
|
+
local character = Players.LocalPlayer.Character
|
|
147
|
+
if generation ~= self.generation then
|
|
148
|
+
self._enabling = false
|
|
149
|
+
local pending = self.pendingGetGravityUp
|
|
150
|
+
if pending then
|
|
151
|
+
self:enable(pending)
|
|
152
|
+
end
|
|
153
|
+
return nil
|
|
154
|
+
end
|
|
155
|
+
-- Phase 2: wait for Animate script + Controller instance
|
|
156
|
+
local animate = character:WaitForChild("Animate", timeout)
|
|
157
|
+
if not animate then
|
|
158
|
+
self:retryEnable(generation, `Animate not found after {timeout}s`)
|
|
159
|
+
return nil
|
|
160
|
+
end
|
|
161
|
+
local animController = animate:WaitForChild("Controller", timeout)
|
|
162
|
+
if not animController then
|
|
163
|
+
self:retryEnable(generation, `Animate.Controller not found after {timeout}s`)
|
|
164
|
+
return nil
|
|
165
|
+
end
|
|
166
|
+
-- Phase 3: wait for Animate module to finish executing
|
|
167
|
+
local loaded = animate:FindFirstChild("Loaded")
|
|
168
|
+
if loaded and not loaded.Value then
|
|
169
|
+
self.logger:Info(`Waiting for Animate to finish loading (gen {generation})`)
|
|
170
|
+
local loadStart = os.clock()
|
|
171
|
+
while not loaded.Value and os.clock() - loadStart < timeout and generation == self.generation do
|
|
172
|
+
task.wait(0.1)
|
|
173
|
+
end
|
|
174
|
+
if not loaded.Value then
|
|
175
|
+
self:retryEnable(generation, `Animate not fully loaded after {timeout}s`)
|
|
176
|
+
return nil
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
if generation ~= self.generation then
|
|
180
|
+
self._enabling = false
|
|
181
|
+
local pending = self.pendingGetGravityUp
|
|
182
|
+
if pending then
|
|
183
|
+
self:enable(pending)
|
|
184
|
+
end
|
|
185
|
+
return nil
|
|
186
|
+
end
|
|
187
|
+
-- Phase 4: construct
|
|
188
|
+
self.logger:Info(`Constructing gravity controller (gen {generation})`)
|
|
189
|
+
local ok, result = pcall(function()
|
|
190
|
+
local gc = self.gravityControllerClass.new(Players.LocalPlayer)
|
|
191
|
+
gc.GetGravityUp = getGravityUp
|
|
192
|
+
return gc
|
|
193
|
+
end)
|
|
194
|
+
self._enabling = false
|
|
195
|
+
if generation ~= self.generation then
|
|
196
|
+
self.logger:Info(`Gravity controller construction completed but generation is stale ({generation} vs {self.generation}), destroying`)
|
|
197
|
+
if ok and result then
|
|
198
|
+
result:Destroy()
|
|
199
|
+
end
|
|
200
|
+
local pending = self.pendingGetGravityUp
|
|
201
|
+
if pending then
|
|
202
|
+
self:enable(pending)
|
|
203
|
+
end
|
|
204
|
+
return nil
|
|
205
|
+
end
|
|
206
|
+
if ok and result then
|
|
207
|
+
self._controller = result
|
|
208
|
+
self.pendingGetGravityUp = nil
|
|
209
|
+
self.retryCount = 0
|
|
210
|
+
self.logger:Info(`Gravity controller enabled (gen {generation})`)
|
|
211
|
+
else
|
|
212
|
+
local _result
|
|
213
|
+
if type(result) == "string" then
|
|
214
|
+
_result = result
|
|
215
|
+
else
|
|
216
|
+
local _condition = result
|
|
217
|
+
if _condition == nil then
|
|
218
|
+
_condition = "Unknown error"
|
|
219
|
+
end
|
|
220
|
+
_result = tostring(_condition)
|
|
221
|
+
end
|
|
222
|
+
local err = _result
|
|
223
|
+
self.logger:Error(`Error enabling gravity controller: {err}`)
|
|
224
|
+
end
|
|
225
|
+
end)
|
|
226
|
+
-- Watchdog: hard-cancel + cleanup + retry
|
|
227
|
+
task.delay(timeout, function()
|
|
228
|
+
if not self._enabling or self.generation ~= generation then
|
|
229
|
+
return nil
|
|
230
|
+
end
|
|
231
|
+
if self.constructionThread then
|
|
232
|
+
pcall(function()
|
|
233
|
+
return task.cancel(self.constructionThread)
|
|
234
|
+
end)
|
|
235
|
+
self.constructionThread = nil
|
|
236
|
+
end
|
|
237
|
+
pcall(function()
|
|
238
|
+
return RunService:UnbindFromRenderStep("GravityStep")
|
|
239
|
+
end)
|
|
240
|
+
local _humanoid = Players.LocalPlayer.Character
|
|
241
|
+
if _humanoid ~= nil then
|
|
242
|
+
_humanoid = _humanoid:FindFirstChildOfClass("Humanoid")
|
|
243
|
+
end
|
|
244
|
+
local humanoid = _humanoid
|
|
245
|
+
if humanoid then
|
|
246
|
+
humanoid.PlatformStand = false
|
|
247
|
+
end
|
|
248
|
+
self:retryEnable(generation, `construction timed out after {timeout}s`)
|
|
249
|
+
end)
|
|
250
|
+
end
|
|
251
|
+
function GravityManager:retryEnable(generation, reason)
|
|
252
|
+
if generation ~= self.generation then
|
|
253
|
+
return nil
|
|
254
|
+
end
|
|
255
|
+
self._enabling = false
|
|
256
|
+
self.retryCount += 1
|
|
257
|
+
if self.retryCount > MAX_GRAVITY_RETRIES then
|
|
258
|
+
self.logger:Error(`Gravity controller failed after {self.retryCount} attempts ({reason}), giving up (gen {generation})`)
|
|
259
|
+
return nil
|
|
260
|
+
end
|
|
261
|
+
self.logger:Warn(`Gravity controller: {reason} (gen {generation}), retrying (attempt {self.retryCount}/{MAX_GRAVITY_RETRIES})`)
|
|
262
|
+
self.generation += 1
|
|
263
|
+
local pending = self.pendingGetGravityUp
|
|
264
|
+
if pending then
|
|
265
|
+
self:enable(pending)
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
-- ── GetGravityUp implementations ─────────────────────────────────────
|
|
47
270
|
local PI2 = math.pi * 2
|
|
48
271
|
local ZERO = Vector3.new(0, 0, 0)
|
|
49
272
|
local LOWER_RADIUS_OFFSET = 3
|
|
@@ -175,6 +398,8 @@ local function getGravityControllerUp(gravityController, oldGravityUp)
|
|
|
175
398
|
return getGroundNormal(gravityController.HRP.CFrame, if gravityController.Humanoid.RigType == Enum.HumanoidRigType.R15 then ZERO else oldGravityUp * 0.35, oldGravityUp)
|
|
176
399
|
end
|
|
177
400
|
exports.installGravityControllerClass = installGravityControllerClass
|
|
401
|
+
exports.wrapGravityUpSaveAttribute = wrapGravityUpSaveAttribute
|
|
178
402
|
exports.getGroundNormal = getGroundNormal
|
|
179
403
|
exports.getGravityControllerUp = getGravityControllerUp
|
|
404
|
+
exports.GravityManager = GravityManager
|
|
180
405
|
return exports
|