@quenty/softshutdown 9.48.1 → 9.50.0
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 +14 -0
- package/package.json +7 -7
- package/src/Client/SoftShutdownServiceClient.lua +36 -13
- package/src/Client/SoftShutdownUI.lua +170 -155
- package/src/Server/SoftShutdownService.lua +28 -14
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,20 @@
|
|
|
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.50.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/softshutdown@9.49.0...@quenty/softshutdown@9.50.0) (2026-07-15)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @quenty/softshutdown
|
|
9
|
+
|
|
10
|
+
# [9.49.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/softshutdown@9.48.1...@quenty/softshutdown@9.49.0) (2026-07-14)
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
- **lint:** resolve selene unused-self and Rx shadowing from --!strict conversions ([3f4a085](https://github.com/Quenty/NevermoreEngine/commit/3f4a085911808146f10a2435efbe9e89dff8edff))
|
|
15
|
+
|
|
16
|
+
### Features
|
|
17
|
+
|
|
18
|
+
- **softshutdown:** convert package to --!strict ([91f7bfa](https://github.com/Quenty/NevermoreEngine/commit/91f7bfa6518461761599d492d83cd3dc6ecb0d4f))
|
|
19
|
+
|
|
6
20
|
## [9.48.1](https://github.com/Quenty/NevermoreEngine/compare/@quenty/softshutdown@9.48.0...@quenty/softshutdown@9.48.1) (2026-05-30)
|
|
7
21
|
|
|
8
22
|
**Note:** Version bump only for package @quenty/softshutdown
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quenty/softshutdown",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.50.0",
|
|
4
4
|
"description": "This service lets you shut down servers without losing a bunch of players. When game.OnClose is called, the script teleports everyone in the server into a reserved server.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Roblox",
|
|
@@ -35,11 +35,11 @@
|
|
|
35
35
|
"@quenty/attributeutils": "14.30.1",
|
|
36
36
|
"@quenty/baseobject": "10.13.0",
|
|
37
37
|
"@quenty/basicpane": "13.31.1",
|
|
38
|
-
"@quenty/bindtocloseservice": "8.
|
|
39
|
-
"@quenty/blend": "12.
|
|
40
|
-
"@quenty/clienttranslator": "14.
|
|
41
|
-
"@quenty/coreguienabler": "12.
|
|
42
|
-
"@quenty/datastore": "13.
|
|
38
|
+
"@quenty/bindtocloseservice": "8.33.0",
|
|
39
|
+
"@quenty/blend": "12.37.0",
|
|
40
|
+
"@quenty/clienttranslator": "14.39.0",
|
|
41
|
+
"@quenty/coreguienabler": "12.34.0",
|
|
42
|
+
"@quenty/datastore": "13.40.0",
|
|
43
43
|
"@quenty/loader": "10.11.0",
|
|
44
44
|
"@quenty/maid": "3.9.0",
|
|
45
45
|
"@quenty/math": "2.7.5",
|
|
@@ -52,5 +52,5 @@
|
|
|
52
52
|
"@quenty/valuebaseutils": "13.30.1",
|
|
53
53
|
"@quenty/valueobject": "13.31.1"
|
|
54
54
|
},
|
|
55
|
-
"gitHead": "
|
|
55
|
+
"gitHead": "d577f1529aa03f5e77534145501e878bde07ca98"
|
|
56
56
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
--!
|
|
1
|
+
--!strict
|
|
2
2
|
--[=[
|
|
3
3
|
@class SoftShutdownServiceClient
|
|
4
4
|
]=]
|
|
@@ -22,6 +22,19 @@ local ValueObject = require("ValueObject")
|
|
|
22
22
|
local SoftShutdownServiceClient = {}
|
|
23
23
|
SoftShutdownServiceClient.ServiceName = "SoftShutdownServiceClient"
|
|
24
24
|
|
|
25
|
+
export type SoftShutdownServiceClient = typeof(setmetatable(
|
|
26
|
+
{} :: {
|
|
27
|
+
_serviceBag: ServiceBag.ServiceBag,
|
|
28
|
+
_maid: Maid.Maid,
|
|
29
|
+
_translator: any,
|
|
30
|
+
_isLobby: AttributeValue.AttributeValue<boolean>,
|
|
31
|
+
_isUpdating: AttributeValue.AttributeValue<boolean>,
|
|
32
|
+
_localTeleportDataSaysIsLobby: ValueObject.ValueObject<boolean>,
|
|
33
|
+
_isArrivingAfterShutdown: ValueObject.ValueObject<boolean>,
|
|
34
|
+
},
|
|
35
|
+
{} :: typeof({ __index = SoftShutdownServiceClient })
|
|
36
|
+
))
|
|
37
|
+
|
|
25
38
|
local DISABLE_CORE_GUI_TYPES = {
|
|
26
39
|
Enum.CoreGuiType.PlayerList,
|
|
27
40
|
Enum.CoreGuiType.Health,
|
|
@@ -31,8 +44,8 @@ local DISABLE_CORE_GUI_TYPES = {
|
|
|
31
44
|
Enum.CoreGuiType.All,
|
|
32
45
|
}
|
|
33
46
|
|
|
34
|
-
function SoftShutdownServiceClient
|
|
35
|
-
assert(not self._serviceBag, "Already initialized")
|
|
47
|
+
function SoftShutdownServiceClient.Init(self: SoftShutdownServiceClient, serviceBag: ServiceBag.ServiceBag): ()
|
|
48
|
+
assert(not (self :: any)._serviceBag, "Already initialized")
|
|
36
49
|
self._serviceBag = assert(serviceBag, "No serviceBag")
|
|
37
50
|
|
|
38
51
|
self._maid = Maid.new()
|
|
@@ -58,7 +71,7 @@ function SoftShutdownServiceClient:Init(serviceBag: ServiceBag.ServiceBag)
|
|
|
58
71
|
isShuttingDown = self._isUpdating:Observe(),
|
|
59
72
|
localTeleportDataSaysIsLobby = self._localTeleportDataSaysIsLobby:Observe(),
|
|
60
73
|
isArrivingAfterShutdown = self._isArrivingAfterShutdown:Observe(),
|
|
61
|
-
}):Subscribe(function(state)
|
|
74
|
+
}):Subscribe(function(state: any)
|
|
62
75
|
if state.isLobby or state.localTeleportDataSaysIsLobby then
|
|
63
76
|
self._maid._shutdownUI = nil
|
|
64
77
|
if not self._maid._lobbyUI then
|
|
@@ -95,7 +108,7 @@ function SoftShutdownServiceClient:Init(serviceBag: ServiceBag.ServiceBag)
|
|
|
95
108
|
end))
|
|
96
109
|
end
|
|
97
110
|
|
|
98
|
-
function SoftShutdownServiceClient
|
|
111
|
+
function SoftShutdownServiceClient._queryIsArrivingAfterShutdown(_self: SoftShutdownServiceClient): boolean
|
|
99
112
|
local data = TeleportService:GetLocalPlayerTeleportData()
|
|
100
113
|
if type(data) == "table" and data.isSoftShutdownArrivingIntoUpdatedServer then
|
|
101
114
|
return true
|
|
@@ -104,7 +117,7 @@ function SoftShutdownServiceClient:_queryIsArrivingAfterShutdown()
|
|
|
104
117
|
end
|
|
105
118
|
end
|
|
106
119
|
|
|
107
|
-
function SoftShutdownServiceClient
|
|
120
|
+
function SoftShutdownServiceClient._queryLocalTeleportInfo(_self: SoftShutdownServiceClient): boolean
|
|
108
121
|
local data = TeleportService:GetLocalPlayerTeleportData()
|
|
109
122
|
if type(data) == "table" and data.isSoftShutdownReserveServer then
|
|
110
123
|
return true
|
|
@@ -113,7 +126,12 @@ function SoftShutdownServiceClient:_queryLocalTeleportInfo()
|
|
|
113
126
|
end
|
|
114
127
|
end
|
|
115
128
|
|
|
116
|
-
function SoftShutdownServiceClient
|
|
129
|
+
function SoftShutdownServiceClient._showSoftShutdownUI(
|
|
130
|
+
self: SoftShutdownServiceClient,
|
|
131
|
+
titleKey: string,
|
|
132
|
+
subtitleKey: string,
|
|
133
|
+
doNotAnimateShow: boolean?
|
|
134
|
+
): (Maid.Maid, ScreenGui)
|
|
117
135
|
local maid = Maid.new()
|
|
118
136
|
|
|
119
137
|
local renderMaid = Maid.new()
|
|
@@ -152,19 +170,24 @@ function SoftShutdownServiceClient:_showSoftShutdownUI(titleKey, subtitleKey, do
|
|
|
152
170
|
|
|
153
171
|
softShutdownUI:Show(doNotAnimateShow)
|
|
154
172
|
|
|
155
|
-
softShutdownUI.Gui
|
|
173
|
+
local gui = softShutdownUI.Gui :: GuiObject
|
|
174
|
+
gui.Parent = screenGui
|
|
156
175
|
|
|
157
176
|
return maid, screenGui
|
|
158
177
|
end
|
|
159
178
|
|
|
160
|
-
function SoftShutdownServiceClient
|
|
179
|
+
function SoftShutdownServiceClient._hideCoreGuiUI(
|
|
180
|
+
_self: SoftShutdownServiceClient,
|
|
181
|
+
maid: Maid.Maid,
|
|
182
|
+
ignoreScreenGui: ScreenGui
|
|
183
|
+
): ()
|
|
161
184
|
maid:GiveTask(CoreGuiEnabler:PushDisable("ModalEnabled"))
|
|
162
185
|
|
|
163
186
|
local playerGui = PlayerGuiUtils.getPlayerGui()
|
|
164
187
|
|
|
165
|
-
local enabledScreenGuis = {}
|
|
188
|
+
local enabledScreenGuis: { [ScreenGui]: ScreenGui } = {}
|
|
166
189
|
|
|
167
|
-
local function handleChild(child)
|
|
190
|
+
local function handleChild(child: Instance)
|
|
168
191
|
if child:IsA("ScreenGui") and child ~= ignoreScreenGui and child.Enabled then
|
|
169
192
|
enabledScreenGuis[child] = child
|
|
170
193
|
child.Enabled = false
|
|
@@ -180,7 +203,7 @@ function SoftShutdownServiceClient:_hideCoreGuiUI(maid, ignoreScreenGui)
|
|
|
180
203
|
end))
|
|
181
204
|
|
|
182
205
|
maid:GiveTask(playerGui.ChildRemoved:Connect(function(child)
|
|
183
|
-
enabledScreenGuis[child] = nil
|
|
206
|
+
enabledScreenGuis[child :: ScreenGui] = nil
|
|
184
207
|
end))
|
|
185
208
|
|
|
186
209
|
maid:GiveTask(function()
|
|
@@ -194,7 +217,7 @@ function SoftShutdownServiceClient:_hideCoreGuiUI(maid, ignoreScreenGui)
|
|
|
194
217
|
end
|
|
195
218
|
end
|
|
196
219
|
|
|
197
|
-
function SoftShutdownServiceClient
|
|
220
|
+
function SoftShutdownServiceClient.Destroy(self: SoftShutdownServiceClient): ()
|
|
198
221
|
self._maid:DoCleaning()
|
|
199
222
|
end
|
|
200
223
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
--!
|
|
1
|
+
--!strict
|
|
2
2
|
--[=[
|
|
3
3
|
@class SoftShutdownUI
|
|
4
4
|
]=]
|
|
@@ -11,6 +11,7 @@ local Workspace = game:GetService("Workspace")
|
|
|
11
11
|
local BasicPane = require("BasicPane")
|
|
12
12
|
local Blend = require("Blend")
|
|
13
13
|
local Math = require("Math")
|
|
14
|
+
local Observable = require("Observable")
|
|
14
15
|
local Rx = require("Rx")
|
|
15
16
|
local SpringObject = require("SpringObject")
|
|
16
17
|
|
|
@@ -18,8 +19,20 @@ local SoftShutdownUI = setmetatable({}, BasicPane)
|
|
|
18
19
|
SoftShutdownUI.ClassName = "SoftShutdownUI"
|
|
19
20
|
SoftShutdownUI.__index = SoftShutdownUI
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
export type SoftShutdownUI =
|
|
23
|
+
typeof(setmetatable(
|
|
24
|
+
{} :: {
|
|
25
|
+
_title: StringValue,
|
|
26
|
+
_subtitle: StringValue,
|
|
27
|
+
_percentVisible: SpringObject.SpringObject<number>,
|
|
28
|
+
_blur: BlurEffect,
|
|
29
|
+
},
|
|
30
|
+
{} :: typeof({ __index = SoftShutdownUI })
|
|
31
|
+
))
|
|
32
|
+
& BasicPane.BasicPane
|
|
33
|
+
|
|
34
|
+
function SoftShutdownUI.new(): SoftShutdownUI
|
|
35
|
+
local self: SoftShutdownUI = setmetatable(BasicPane.new() :: any, SoftShutdownUI)
|
|
23
36
|
|
|
24
37
|
self._title = Instance.new("StringValue")
|
|
25
38
|
self._title.Value = ""
|
|
@@ -31,7 +44,7 @@ function SoftShutdownUI.new()
|
|
|
31
44
|
|
|
32
45
|
self._percentVisible = self._maid:Add(SpringObject.new(0, 40))
|
|
33
46
|
|
|
34
|
-
self._maid:GiveTask(self.VisibleChanged:Connect(function(isVisible, doNotAnimate)
|
|
47
|
+
self._maid:GiveTask(self.VisibleChanged:Connect(function(isVisible: boolean, doNotAnimate: boolean)
|
|
35
48
|
self._percentVisible.t = isVisible and 1 or 0
|
|
36
49
|
if doNotAnimate then
|
|
37
50
|
self._percentVisible.p = self._percentVisible.t
|
|
@@ -46,22 +59,22 @@ function SoftShutdownUI.new()
|
|
|
46
59
|
self._blur.Parent = Workspace.CurrentCamera
|
|
47
60
|
self._maid:GiveTask(self._blur)
|
|
48
61
|
|
|
49
|
-
self._maid:GiveTask(self:_render():Subscribe(function(gui)
|
|
62
|
+
self._maid:GiveTask(self:_render():Subscribe(function(gui: GuiObject)
|
|
50
63
|
self.Gui = gui
|
|
51
64
|
end))
|
|
52
65
|
|
|
53
66
|
return self
|
|
54
67
|
end
|
|
55
68
|
|
|
56
|
-
function SoftShutdownUI
|
|
69
|
+
function SoftShutdownUI.SetTitle(self: SoftShutdownUI, text: string): ()
|
|
57
70
|
self._title.Value = text
|
|
58
71
|
end
|
|
59
72
|
|
|
60
|
-
function SoftShutdownUI
|
|
73
|
+
function SoftShutdownUI.SetSubtitle(self: SoftShutdownUI, text: string): ()
|
|
61
74
|
self._subtitle.Value = text
|
|
62
75
|
end
|
|
63
76
|
|
|
64
|
-
function SoftShutdownUI
|
|
77
|
+
function SoftShutdownUI._render(self: SoftShutdownUI): Observable.Observable<GuiObject>
|
|
65
78
|
local percentVisible = self._percentVisible:ObserveRenderStepped()
|
|
66
79
|
local transparency = Blend.Computed(percentVisible, function(value)
|
|
67
80
|
return 1 - value
|
|
@@ -74,174 +87,176 @@ function SoftShutdownUI:_render()
|
|
|
74
87
|
self._blur.Enabled = percent > 0
|
|
75
88
|
end))
|
|
76
89
|
|
|
77
|
-
return
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
Blend.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
90
|
+
return (
|
|
91
|
+
Blend.New "Frame" {
|
|
92
|
+
Name = "SoftShutdownUI",
|
|
93
|
+
Size = UDim2.fromScale(1, 1),
|
|
94
|
+
AnchorPoint = Vector2.new(0.5, 0.5),
|
|
95
|
+
Position = UDim2.fromScale(0.5, 0.5),
|
|
96
|
+
Active = Blend.Computed(percentVisible, function(visible)
|
|
97
|
+
return visible > 0
|
|
98
|
+
end),
|
|
99
|
+
Visible = Blend.Computed(percentVisible, function(visible)
|
|
100
|
+
return visible > 0
|
|
101
|
+
end),
|
|
102
|
+
BackgroundColor3 = backgroundColor,
|
|
103
|
+
BackgroundTransparency = Blend.Computed(percentVisible, function(visible)
|
|
104
|
+
return Math.map(visible, 0, 1, 1, 0.4)
|
|
105
|
+
end),
|
|
106
|
+
|
|
107
|
+
[Blend.Children] = {
|
|
108
|
+
Blend.New "UIPadding" {
|
|
109
|
+
PaddingLeft = UDim.new(0, 20),
|
|
110
|
+
PaddingRight = UDim.new(0, 20),
|
|
111
|
+
PaddingTop = UDim.new(0, 20),
|
|
112
|
+
PaddingBottom = UDim.new(0, 20),
|
|
113
|
+
},
|
|
100
114
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
115
|
+
Blend.New "Frame" {
|
|
116
|
+
Name = "ContentContainer",
|
|
117
|
+
Size = UDim2.fromScale(1, 1),
|
|
118
|
+
AnchorPoint = Vector2.new(0.5, 0.5),
|
|
119
|
+
Position = UDim2.fromScale(0.5, 0.5),
|
|
120
|
+
BackgroundTransparency = 1,
|
|
121
|
+
|
|
122
|
+
[Blend.Children] = {
|
|
123
|
+
Blend.New "UIScale" {
|
|
124
|
+
Scale = Blend.Computed(percentVisible, function(visible)
|
|
125
|
+
return 0.7 + 0.3 * visible
|
|
126
|
+
end),
|
|
127
|
+
},
|
|
114
128
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
129
|
+
Blend.New "Frame" {
|
|
130
|
+
Name = "ImageLabelContainer",
|
|
131
|
+
Size = UDim2.fromOffset(80, 80),
|
|
132
|
+
BackgroundTransparency = 1,
|
|
133
|
+
LayoutOrder = 1,
|
|
134
|
+
|
|
135
|
+
[Blend.Children] = {
|
|
136
|
+
Blend.New "ImageLabel" {
|
|
137
|
+
Size = UDim2.fromScale(1, 1),
|
|
138
|
+
ImageTransparency = transparency,
|
|
139
|
+
BackgroundTransparency = 1,
|
|
140
|
+
Image = "rbxassetid://6031302916",
|
|
141
|
+
},
|
|
127
142
|
},
|
|
128
143
|
},
|
|
129
|
-
},
|
|
130
144
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
145
|
+
Blend.New "Frame" {
|
|
146
|
+
Name = "LabelContainer",
|
|
147
|
+
Size = UDim2.new(1, 0, 0, 80),
|
|
148
|
+
Position = UDim2.fromScale(0.5, 0.5),
|
|
149
|
+
AnchorPoint = Vector2.new(0.5, 0.5),
|
|
150
|
+
BackgroundTransparency = 1,
|
|
151
|
+
LayoutOrder = 2,
|
|
152
|
+
|
|
153
|
+
[Blend.Children] = {
|
|
154
|
+
Blend.New "TextLabel" {
|
|
155
|
+
Name = "TitleLabel",
|
|
156
|
+
BackgroundTransparency = 1,
|
|
157
|
+
Position = UDim2.fromScale(0.5, 0),
|
|
158
|
+
Size = UDim2.fromScale(1, 0.6),
|
|
159
|
+
AnchorPoint = Vector2.new(0.5, 0),
|
|
160
|
+
Text = self._title,
|
|
161
|
+
Font = Enum.Font.SourceSansBold,
|
|
162
|
+
TextTransparency = transparency,
|
|
163
|
+
TextColor3 = foregroundColor,
|
|
164
|
+
LayoutOrder = 1,
|
|
165
|
+
TextScaled = true,
|
|
166
|
+
},
|
|
153
167
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
168
|
+
Blend.New "TextLabel" {
|
|
169
|
+
Name = "SubtitleLabel",
|
|
170
|
+
BackgroundTransparency = 1,
|
|
171
|
+
Position = UDim2.fromScale(0.5, 1),
|
|
172
|
+
Size = UDim2.fromScale(1, 0.3),
|
|
173
|
+
AnchorPoint = Vector2.new(0.5, 1),
|
|
174
|
+
Text = self._subtitle,
|
|
175
|
+
Font = Enum.Font.SourceSansLight,
|
|
176
|
+
TextTransparency = transparency,
|
|
177
|
+
TextColor3 = foregroundColor,
|
|
178
|
+
LayoutOrder = 2,
|
|
179
|
+
TextScaled = true,
|
|
180
|
+
},
|
|
167
181
|
|
|
168
|
-
|
|
169
|
-
|
|
182
|
+
Blend.New "UIAspectRatioConstraint" {
|
|
183
|
+
AspectRatio = 5,
|
|
184
|
+
},
|
|
170
185
|
},
|
|
171
186
|
},
|
|
172
|
-
},
|
|
173
187
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
188
|
+
Blend.New "Frame" {
|
|
189
|
+
Name = "Spacer",
|
|
190
|
+
BackgroundTransparency = 1,
|
|
191
|
+
Size = UDim2.fromOffset(0, 50),
|
|
192
|
+
LayoutOrder = 3,
|
|
193
|
+
},
|
|
180
194
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
195
|
+
Blend.New "Frame" {
|
|
196
|
+
Name = "LoadingLabel",
|
|
197
|
+
Position = UDim2.fromScale(0.5, 0.5),
|
|
198
|
+
AnchorPoint = Vector2.new(0.5, 0.5),
|
|
199
|
+
LayoutOrder = 4,
|
|
200
|
+
Size = UDim2.fromScale(0.25, 0.25),
|
|
201
|
+
BackgroundTransparency = 1,
|
|
202
|
+
|
|
203
|
+
[Blend.Children] = {
|
|
204
|
+
Blend.New "Frame" {
|
|
205
|
+
Name = "RobloxLogo",
|
|
206
|
+
Size = UDim2.fromScale(1, 1),
|
|
207
|
+
BackgroundColor3 = foregroundColor,
|
|
208
|
+
AnchorPoint = Vector2.new(0.5, 0.5),
|
|
209
|
+
Position = UDim2.fromScale(0.5, 0.5),
|
|
210
|
+
|
|
211
|
+
BackgroundTransparency = transparency,
|
|
212
|
+
Rotation = (Rx.fromSignal(RunService.RenderStepped) :: any):Pipe({
|
|
213
|
+
Rx.map(function(): any
|
|
214
|
+
-- tick persists between sessions
|
|
215
|
+
local t = tick() * math.pi * 1.5
|
|
216
|
+
local smallerWave = math.sin(t)
|
|
217
|
+
local percent = (math.sin(t - math.pi / 2) + 1) / 2
|
|
218
|
+
|
|
219
|
+
if smallerWave > 0 then
|
|
220
|
+
return 15 + percent * 360
|
|
221
|
+
else
|
|
222
|
+
return 15
|
|
223
|
+
end
|
|
224
|
+
end),
|
|
225
|
+
}),
|
|
226
|
+
|
|
227
|
+
[Blend.Children] = {
|
|
228
|
+
Blend.New "Frame" {
|
|
229
|
+
BackgroundColor3 = backgroundColor,
|
|
230
|
+
Size = UDim2.fromScale(4 / 14, 4 / 14),
|
|
231
|
+
AnchorPoint = Vector2.new(0.5, 0.5),
|
|
232
|
+
Position = UDim2.fromScale(0.5, 0.5),
|
|
233
|
+
BackgroundTransparency = transparency,
|
|
234
|
+
},
|
|
220
235
|
},
|
|
221
236
|
},
|
|
222
|
-
},
|
|
223
237
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
238
|
+
Blend.New "UIAspectRatioConstraint" {
|
|
239
|
+
AspectRatio = 1,
|
|
240
|
+
},
|
|
227
241
|
|
|
228
|
-
|
|
229
|
-
|
|
242
|
+
Blend.New "UISizeConstraint" {
|
|
243
|
+
MaxSize = Vector2.new(math.huge, 50),
|
|
244
|
+
},
|
|
230
245
|
},
|
|
231
246
|
},
|
|
232
|
-
},
|
|
233
247
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
248
|
+
Blend.New "UIListLayout" {
|
|
249
|
+
FillDirection = Enum.FillDirection.Vertical,
|
|
250
|
+
SortOrder = Enum.SortOrder.LayoutOrder,
|
|
251
|
+
HorizontalAlignment = Enum.HorizontalAlignment.Center,
|
|
252
|
+
VerticalAlignment = Enum.VerticalAlignment.Center,
|
|
253
|
+
Padding = UDim.new(0, 10),
|
|
254
|
+
},
|
|
240
255
|
},
|
|
241
256
|
},
|
|
242
257
|
},
|
|
243
|
-
}
|
|
244
|
-
|
|
258
|
+
}
|
|
259
|
+
) :: any
|
|
245
260
|
end
|
|
246
261
|
|
|
247
262
|
return SoftShutdownUI
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
--!
|
|
1
|
+
--!strict
|
|
2
2
|
--[=[
|
|
3
3
|
This service lets you shut down servers without losing a bunch of players.
|
|
4
4
|
When game.OnClose is called, the script teleports everyone in the server
|
|
@@ -22,6 +22,7 @@ local Players = game:GetService("Players")
|
|
|
22
22
|
local RunService = game:GetService("RunService")
|
|
23
23
|
local Workspace = game:GetService("Workspace")
|
|
24
24
|
|
|
25
|
+
local BindToCloseService = require("BindToCloseService")
|
|
25
26
|
local DataStorePromises = require("DataStorePromises")
|
|
26
27
|
local Maid = require("Maid")
|
|
27
28
|
local Promise = require("Promise")
|
|
@@ -32,6 +33,16 @@ local TeleportServiceUtils = require("TeleportServiceUtils")
|
|
|
32
33
|
local SoftShutdownService = {}
|
|
33
34
|
SoftShutdownService.ServiceName = "SoftShutdownService"
|
|
34
35
|
|
|
36
|
+
export type SoftShutdownService = typeof(setmetatable(
|
|
37
|
+
{} :: {
|
|
38
|
+
_maid: Maid.Maid,
|
|
39
|
+
_serviceBag: ServiceBag.ServiceBag,
|
|
40
|
+
_bindToCloseService: BindToCloseService.BindToCloseService,
|
|
41
|
+
_dataStore: DataStore,
|
|
42
|
+
},
|
|
43
|
+
{} :: typeof({ __index = SoftShutdownService })
|
|
44
|
+
))
|
|
45
|
+
|
|
35
46
|
--[=[
|
|
36
47
|
Initialize the service. Should be done via [ServiceBag].
|
|
37
48
|
|
|
@@ -42,11 +53,11 @@ SoftShutdownService.ServiceName = "SoftShutdownService"
|
|
|
42
53
|
|
|
43
54
|
@param serviceBag ServiceBag
|
|
44
55
|
]=]
|
|
45
|
-
function SoftShutdownService
|
|
56
|
+
function SoftShutdownService.Init(self: SoftShutdownService, serviceBag: ServiceBag.ServiceBag): ()
|
|
46
57
|
self._maid = Maid.new()
|
|
47
58
|
self._serviceBag = assert(serviceBag, "No serviceBag")
|
|
48
59
|
|
|
49
|
-
self._bindToCloseService = self._serviceBag:GetService(
|
|
60
|
+
self._bindToCloseService = self._serviceBag:GetService(BindToCloseService) :: any
|
|
50
61
|
self._serviceBag:GetService(require("SoftShutdownTranslator"))
|
|
51
62
|
|
|
52
63
|
self._dataStore = DataStoreService:GetDataStore("IsSoftShutdownServer")
|
|
@@ -62,11 +73,11 @@ function SoftShutdownService:Init(serviceBag: ServiceBag.ServiceBag)
|
|
|
62
73
|
end)
|
|
63
74
|
end
|
|
64
75
|
|
|
65
|
-
function SoftShutdownService
|
|
76
|
+
function SoftShutdownService._isReservedServer(_self: SoftShutdownService): boolean
|
|
66
77
|
return game.PrivateServerId ~= "" and game.PrivateServerOwnerId == 0
|
|
67
78
|
end
|
|
68
79
|
|
|
69
|
-
function SoftShutdownService
|
|
80
|
+
function SoftShutdownService._promiseIsLobby(self: SoftShutdownService): Promise.Promise<boolean>
|
|
70
81
|
if not self:_isReservedServer() then
|
|
71
82
|
return Promise.resolved(false)
|
|
72
83
|
end
|
|
@@ -74,7 +85,7 @@ function SoftShutdownService:_promiseIsLobby()
|
|
|
74
85
|
return self._maid:GivePromise(DataStorePromises.getAsync(self._dataStore, game.PrivateServerId))
|
|
75
86
|
end
|
|
76
87
|
|
|
77
|
-
function SoftShutdownService
|
|
88
|
+
function SoftShutdownService._promiseTeleportPlayersToLobby(self: SoftShutdownService): Promise.Promise<()>
|
|
78
89
|
local players = Players:GetPlayers()
|
|
79
90
|
if RunService:IsStudio() or #players == 0 or game.JobId == "" then
|
|
80
91
|
return Promise.resolved()
|
|
@@ -90,7 +101,7 @@ function SoftShutdownService:_promiseTeleportPlayersToLobby()
|
|
|
90
101
|
|
|
91
102
|
-- Collect any players remaining
|
|
92
103
|
local remainingPlayers = {}
|
|
93
|
-
self._maid._playerAddedCollector = Players.PlayerAdded:Connect(function(player)
|
|
104
|
+
(self._maid :: any)._playerAddedCollector = Players.PlayerAdded:Connect(function(player: Player)
|
|
94
105
|
table.insert(remainingPlayers, player)
|
|
95
106
|
end)
|
|
96
107
|
|
|
@@ -102,7 +113,7 @@ function SoftShutdownService:_promiseTeleportPlayersToLobby()
|
|
|
102
113
|
return TeleportServiceUtils.promiseTeleport(game.PlaceId, players, initialTeleportOptions)
|
|
103
114
|
end)
|
|
104
115
|
:Then(function(teleportResult)
|
|
105
|
-
self._maid._playerAddedCollector = nil
|
|
116
|
+
(self._maid :: any)._playerAddedCollector = nil
|
|
106
117
|
|
|
107
118
|
-- Construct new teleport options
|
|
108
119
|
local newTeleportOptions = Instance.new("TeleportOptions")
|
|
@@ -122,7 +133,7 @@ function SoftShutdownService:_promiseTeleportPlayersToLobby()
|
|
|
122
133
|
)
|
|
123
134
|
end
|
|
124
135
|
|
|
125
|
-
self._maid:GiveTask(Players.PlayerAdded:Connect(function(player)
|
|
136
|
+
self._maid:GiveTask(Players.PlayerAdded:Connect(function(player: Player)
|
|
126
137
|
table.insert(
|
|
127
138
|
promises,
|
|
128
139
|
TeleportServiceUtils.promiseTeleport(game.PlaceId, { player }, newTeleportOptions)
|
|
@@ -130,7 +141,10 @@ function SoftShutdownService:_promiseTeleportPlayersToLobby()
|
|
|
130
141
|
end))
|
|
131
142
|
|
|
132
143
|
-- We hope this works!
|
|
133
|
-
table.insert(
|
|
144
|
+
table.insert(
|
|
145
|
+
promises,
|
|
146
|
+
DataStorePromises.setAsync(self._dataStore, teleportResult.PrivateServerId, true :: any)
|
|
147
|
+
)
|
|
134
148
|
|
|
135
149
|
return Promise.spawn(function(resolve)
|
|
136
150
|
while #Players:GetPlayers() > 0 and self:_containsPending(promises) do
|
|
@@ -142,7 +156,7 @@ function SoftShutdownService:_promiseTeleportPlayersToLobby()
|
|
|
142
156
|
end)
|
|
143
157
|
end
|
|
144
158
|
|
|
145
|
-
function SoftShutdownService
|
|
159
|
+
function SoftShutdownService._containsPending(_self: SoftShutdownService, promises: { any }): boolean
|
|
146
160
|
for _, item in promises do
|
|
147
161
|
if item:IsPending() then
|
|
148
162
|
return true
|
|
@@ -152,7 +166,7 @@ function SoftShutdownService:_containsPending(promises)
|
|
|
152
166
|
return false
|
|
153
167
|
end
|
|
154
168
|
|
|
155
|
-
function SoftShutdownService
|
|
169
|
+
function SoftShutdownService._promiseRedirectAllPlayers(self: SoftShutdownService): Promise.Promise<()>
|
|
156
170
|
Workspace:SetAttribute(SoftShutdownConstants.IS_SOFT_SHUTDOWN_LOBBY_ATTRIBUTE, true)
|
|
157
171
|
|
|
158
172
|
-- Wait for some players
|
|
@@ -174,7 +188,7 @@ function SoftShutdownService:_promiseRedirectAllPlayers()
|
|
|
174
188
|
})
|
|
175
189
|
|
|
176
190
|
-- Teleport all remaining players
|
|
177
|
-
self._maid:GiveTask(Players.PlayerAdded:Connect(function(player)
|
|
191
|
+
self._maid:GiveTask(Players.PlayerAdded:Connect(function(player: Player)
|
|
178
192
|
task.wait(1) -- Let the teleport GUI be set
|
|
179
193
|
TeleportServiceUtils.promiseTeleport(game.PlaceId, { player }, teleportOptions)
|
|
180
194
|
end))
|
|
@@ -184,7 +198,7 @@ function SoftShutdownService:_promiseRedirectAllPlayers()
|
|
|
184
198
|
end)
|
|
185
199
|
end
|
|
186
200
|
|
|
187
|
-
function SoftShutdownService
|
|
201
|
+
function SoftShutdownService.Destroy(self: SoftShutdownService): ()
|
|
188
202
|
self._maid:DoCleaning()
|
|
189
203
|
end
|
|
190
204
|
|