@quenty/softshutdown 9.48.0 → 9.49.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 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.49.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/softshutdown@9.48.1...@quenty/softshutdown@9.49.0) (2026-07-14)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **lint:** resolve selene unused-self and Rx shadowing from --!strict conversions ([3f4a085](https://github.com/Quenty/NevermoreEngine/commit/3f4a085911808146f10a2435efbe9e89dff8edff))
11
+
12
+ ### Features
13
+
14
+ - **softshutdown:** convert package to --!strict ([91f7bfa](https://github.com/Quenty/NevermoreEngine/commit/91f7bfa6518461761599d492d83cd3dc6ecb0d4f))
15
+
16
+ ## [9.48.1](https://github.com/Quenty/NevermoreEngine/compare/@quenty/softshutdown@9.48.0...@quenty/softshutdown@9.48.1) (2026-05-30)
17
+
18
+ **Note:** Version bump only for package @quenty/softshutdown
19
+
6
20
  # [9.48.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/softshutdown@9.47.0...@quenty/softshutdown@9.48.0) (2026-05-29)
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.48.0",
3
+ "version": "9.49.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",
@@ -32,25 +32,25 @@
32
32
  "access": "public"
33
33
  },
34
34
  "dependencies": {
35
- "@quenty/attributeutils": "14.30.0",
35
+ "@quenty/attributeutils": "14.30.1",
36
36
  "@quenty/baseobject": "10.13.0",
37
- "@quenty/basicpane": "13.31.0",
38
- "@quenty/bindtocloseservice": "8.32.0",
39
- "@quenty/blend": "12.36.0",
40
- "@quenty/clienttranslator": "14.37.0",
41
- "@quenty/coreguienabler": "12.33.0",
42
- "@quenty/datastore": "13.39.0",
37
+ "@quenty/basicpane": "13.31.1",
38
+ "@quenty/bindtocloseservice": "8.33.0",
39
+ "@quenty/blend": "12.37.0",
40
+ "@quenty/clienttranslator": "14.38.0",
41
+ "@quenty/coreguienabler": "12.33.1",
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",
46
- "@quenty/promise": "10.18.0",
47
- "@quenty/rx": "13.28.2",
48
- "@quenty/servicebag": "11.18.0",
46
+ "@quenty/promise": "10.18.1",
47
+ "@quenty/rx": "13.28.3",
48
+ "@quenty/servicebag": "11.18.1",
49
49
  "@quenty/table": "3.9.2",
50
- "@quenty/teleportserviceutils": "9.31.0",
51
- "@quenty/uiobjectutils": "6.29.0",
52
- "@quenty/valuebaseutils": "13.30.0",
53
- "@quenty/valueobject": "13.31.0"
50
+ "@quenty/teleportserviceutils": "9.31.1",
51
+ "@quenty/uiobjectutils": "6.29.1",
52
+ "@quenty/valuebaseutils": "13.30.1",
53
+ "@quenty/valueobject": "13.31.1"
54
54
  },
55
- "gitHead": "f4a374a0a294ee8900aa5cb68ab138b0acf3e0ae"
55
+ "gitHead": "13f0162f4971a77378e55e9b7236aea94f0dd3a8"
56
56
  }
@@ -1,4 +1,4 @@
1
- --!nonstrict
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:Init(serviceBag: ServiceBag.ServiceBag)
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:_queryIsArrivingAfterShutdown()
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:_queryLocalTeleportInfo()
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:_showSoftShutdownUI(titleKey, subtitleKey, doNotAnimateShow)
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.Parent = screenGui
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:_hideCoreGuiUI(maid, ignoreScreenGui)
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:Destroy()
220
+ function SoftShutdownServiceClient.Destroy(self: SoftShutdownServiceClient): ()
198
221
  self._maid:DoCleaning()
199
222
  end
200
223
 
@@ -1,4 +1,4 @@
1
- --!nonstrict
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
- function SoftShutdownUI.new()
22
- local self = setmetatable(BasicPane.new(), SoftShutdownUI)
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:SetTitle(text)
69
+ function SoftShutdownUI.SetTitle(self: SoftShutdownUI, text: string): ()
57
70
  self._title.Value = text
58
71
  end
59
72
 
60
- function SoftShutdownUI:SetSubtitle(text)
73
+ function SoftShutdownUI.SetSubtitle(self: SoftShutdownUI, text: string): ()
61
74
  self._subtitle.Value = text
62
75
  end
63
76
 
64
- function SoftShutdownUI:_render()
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 Blend.New "Frame" {
78
- Name = "SoftShutdownUI",
79
- Size = UDim2.fromScale(1, 1),
80
- AnchorPoint = Vector2.new(0.5, 0.5),
81
- Position = UDim2.fromScale(0.5, 0.5),
82
- Active = Blend.Computed(percentVisible, function(visible)
83
- return visible > 0
84
- end),
85
- Visible = Blend.Computed(percentVisible, function(visible)
86
- return visible > 0
87
- end),
88
- BackgroundColor3 = backgroundColor,
89
- BackgroundTransparency = Blend.Computed(percentVisible, function(visible)
90
- return Math.map(visible, 0, 1, 1, 0.4)
91
- end),
92
-
93
- [Blend.Children] = {
94
- Blend.New "UIPadding" {
95
- PaddingLeft = UDim.new(0, 20),
96
- PaddingRight = UDim.new(0, 20),
97
- PaddingTop = UDim.new(0, 20),
98
- PaddingBottom = UDim.new(0, 20),
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
- Blend.New "Frame" {
102
- Name = "ContentContainer",
103
- Size = UDim2.fromScale(1, 1),
104
- AnchorPoint = Vector2.new(0.5, 0.5),
105
- Position = UDim2.fromScale(0.5, 0.5),
106
- BackgroundTransparency = 1,
107
-
108
- [Blend.Children] = {
109
- Blend.New "UIScale" {
110
- Scale = Blend.Computed(percentVisible, function(visible)
111
- return 0.7 + 0.3 * visible
112
- end),
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
- Blend.New "Frame" {
116
- Name = "ImageLabelContainer",
117
- Size = UDim2.fromOffset(80, 80),
118
- BackgroundTransparency = 1,
119
- LayoutOrder = 1,
120
-
121
- [Blend.Children] = {
122
- Blend.New "ImageLabel" {
123
- Size = UDim2.fromScale(1, 1),
124
- ImageTransparency = transparency,
125
- BackgroundTransparency = 1,
126
- Image = "rbxassetid://6031302916",
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
- Blend.New "Frame" {
132
- Name = "LabelContainer",
133
- Size = UDim2.new(1, 0, 0, 80),
134
- Position = UDim2.fromScale(0.5, 0.5),
135
- AnchorPoint = Vector2.new(0.5, 0.5),
136
- BackgroundTransparency = 1,
137
- LayoutOrder = 2,
138
-
139
- [Blend.Children] = {
140
- Blend.New "TextLabel" {
141
- Name = "TitleLabel",
142
- BackgroundTransparency = 1,
143
- Position = UDim2.fromScale(0.5, 0),
144
- Size = UDim2.fromScale(1, 0.6),
145
- AnchorPoint = Vector2.new(0.5, 0),
146
- Text = self._title,
147
- Font = Enum.Font.SourceSansBold,
148
- TextTransparency = transparency,
149
- TextColor3 = foregroundColor,
150
- LayoutOrder = 1,
151
- TextScaled = true,
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
- Blend.New "TextLabel" {
155
- Name = "SubtitleLabel",
156
- BackgroundTransparency = 1,
157
- Position = UDim2.fromScale(0.5, 1),
158
- Size = UDim2.fromScale(1, 0.3),
159
- AnchorPoint = Vector2.new(0.5, 1),
160
- Text = self._subtitle,
161
- Font = Enum.Font.SourceSansLight,
162
- TextTransparency = transparency,
163
- TextColor3 = foregroundColor,
164
- LayoutOrder = 2,
165
- TextScaled = true,
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
- Blend.New "UIAspectRatioConstraint" {
169
- AspectRatio = 5,
182
+ Blend.New "UIAspectRatioConstraint" {
183
+ AspectRatio = 5,
184
+ },
170
185
  },
171
186
  },
172
- },
173
187
 
174
- Blend.New "Frame" {
175
- Name = "Spacer",
176
- BackgroundTransparency = 1,
177
- Size = UDim2.fromOffset(0, 50),
178
- LayoutOrder = 3,
179
- },
188
+ Blend.New "Frame" {
189
+ Name = "Spacer",
190
+ BackgroundTransparency = 1,
191
+ Size = UDim2.fromOffset(0, 50),
192
+ LayoutOrder = 3,
193
+ },
180
194
 
181
- Blend.New "Frame" {
182
- Name = "LoadingLabel",
183
- Position = UDim2.fromScale(0.5, 0.5),
184
- AnchorPoint = Vector2.new(0.5, 0.5),
185
- LayoutOrder = 4,
186
- Size = UDim2.fromScale(0.25, 0.25),
187
- BackgroundTransparency = 1,
188
-
189
- [Blend.Children] = {
190
- Blend.New "Frame" {
191
- Name = "RobloxLogo",
192
- Size = UDim2.fromScale(1, 1),
193
- BackgroundColor3 = foregroundColor,
194
- AnchorPoint = Vector2.new(0.5, 0.5),
195
- Position = UDim2.fromScale(0.5, 0.5),
196
-
197
- BackgroundTransparency = transparency,
198
- Rotation = Rx.fromSignal(RunService.RenderStepped):Pipe({
199
- Rx.map(function()
200
- -- tick persists between sessions
201
- local t = tick() * math.pi * 1.5
202
- local smallerWave = math.sin(t)
203
- local percent = (math.sin(t - math.pi / 2) + 1) / 2
204
-
205
- if smallerWave > 0 then
206
- return 15 + percent * 360
207
- else
208
- return 15
209
- end
210
- end),
211
- }),
212
-
213
- [Blend.Children] = {
214
- Blend.New "Frame" {
215
- BackgroundColor3 = backgroundColor,
216
- Size = UDim2.fromScale(4 / 14, 4 / 14),
217
- AnchorPoint = Vector2.new(0.5, 0.5),
218
- Position = UDim2.fromScale(0.5, 0.5),
219
- BackgroundTransparency = transparency,
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
- Blend.New "UIAspectRatioConstraint" {
225
- AspectRatio = 1,
226
- },
238
+ Blend.New "UIAspectRatioConstraint" {
239
+ AspectRatio = 1,
240
+ },
227
241
 
228
- Blend.New "UISizeConstraint" {
229
- MaxSize = Vector2.new(math.huge, 50),
242
+ Blend.New "UISizeConstraint" {
243
+ MaxSize = Vector2.new(math.huge, 50),
244
+ },
230
245
  },
231
246
  },
232
- },
233
247
 
234
- Blend.New "UIListLayout" {
235
- FillDirection = Enum.FillDirection.Vertical,
236
- SortOrder = Enum.SortOrder.LayoutOrder,
237
- HorizontalAlignment = Enum.HorizontalAlignment.Center,
238
- VerticalAlignment = Enum.VerticalAlignment.Center,
239
- Padding = UDim.new(0, 10),
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
- --!nonstrict
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:Init(serviceBag: ServiceBag.ServiceBag)
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(require("BindToCloseService"))
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:_isReservedServer(): boolean
76
+ function SoftShutdownService._isReservedServer(_self: SoftShutdownService): boolean
66
77
  return game.PrivateServerId ~= "" and game.PrivateServerOwnerId == 0
67
78
  end
68
79
 
69
- function SoftShutdownService:_promiseIsLobby()
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:_promiseTeleportPlayersToLobby()
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(promises, DataStorePromises.setAsync(self._dataStore, teleportResult.PrivateServerId, true))
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:_containsPending(promises)
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:_promiseRedirectAllPlayers()
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:Destroy()
201
+ function SoftShutdownService.Destroy(self: SoftShutdownService): ()
188
202
  self._maid:DoCleaning()
189
203
  end
190
204