@quenty/gameproductservice 14.0.0 → 14.1.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 +11 -0
- package/package.json +24 -24
- package/src/Client/GameProductServiceClient.lua +15 -1
- package/src/Server/GameProductService.lua +26 -0
- package/src/Shared/Helpers/GameProductServiceHelper.lua +16 -0
- package/src/Shared/Trackers/PlayerAssetMarketTracker.lua +23 -16
- package/src/Shared/Trackers/PlayerMarketeer.lua +42 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [14.1.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/gameproductservice@14.0.0...@quenty/gameproductservice@14.1.0) (2024-03-09)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* Add PromisePlayerPromptClosed() to GameProductService ([1a8ced1](https://github.com/Quenty/NevermoreEngine/commit/1a8ced1c83c7318107fe5a92bded8559bad3c06b))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
6
17
|
# [14.0.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/gameproductservice@13.0.0...@quenty/gameproductservice@14.0.0) (2024-02-14)
|
|
7
18
|
|
|
8
19
|
**Note:** Version bump only for package @quenty/gameproductservice
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quenty/gameproductservice",
|
|
3
|
-
"version": "14.
|
|
3
|
+
"version": "14.1.0",
|
|
4
4
|
"description": "Generalized monetization system for handling products and purchases correctly.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Roblox",
|
|
@@ -27,31 +27,31 @@
|
|
|
27
27
|
"access": "public"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@quenty/attributeutils": "^14.
|
|
31
|
-
"@quenty/baseobject": "^10.
|
|
32
|
-
"@quenty/binder": "^14.
|
|
33
|
-
"@quenty/brio": "^14.
|
|
30
|
+
"@quenty/attributeutils": "^14.1.0",
|
|
31
|
+
"@quenty/baseobject": "^10.1.0",
|
|
32
|
+
"@quenty/binder": "^14.1.0",
|
|
33
|
+
"@quenty/brio": "^14.1.0",
|
|
34
34
|
"@quenty/enumutils": "^3.1.0",
|
|
35
|
-
"@quenty/gameconfig": "^12.
|
|
36
|
-
"@quenty/instanceutils": "^13.
|
|
37
|
-
"@quenty/loader": "^10.
|
|
38
|
-
"@quenty/maid": "^3.
|
|
39
|
-
"@quenty/marketplaceutils": "^11.
|
|
40
|
-
"@quenty/observablecollection": "^12.
|
|
41
|
-
"@quenty/playerbinder": "^14.
|
|
42
|
-
"@quenty/playerutils": "^8.
|
|
43
|
-
"@quenty/promise": "^10.
|
|
44
|
-
"@quenty/promisemaid": "^5.
|
|
45
|
-
"@quenty/receiptprocessing": "^7.
|
|
46
|
-
"@quenty/remoting": "^12.
|
|
47
|
-
"@quenty/rx": "^13.
|
|
48
|
-
"@quenty/rxbinderutils": "^14.
|
|
49
|
-
"@quenty/servicebag": "^11.
|
|
50
|
-
"@quenty/signal": "^7.
|
|
51
|
-
"@quenty/statestack": "^14.
|
|
35
|
+
"@quenty/gameconfig": "^12.1.0",
|
|
36
|
+
"@quenty/instanceutils": "^13.1.0",
|
|
37
|
+
"@quenty/loader": "^10.1.0",
|
|
38
|
+
"@quenty/maid": "^3.1.0",
|
|
39
|
+
"@quenty/marketplaceutils": "^11.1.0",
|
|
40
|
+
"@quenty/observablecollection": "^12.1.0",
|
|
41
|
+
"@quenty/playerbinder": "^14.1.0",
|
|
42
|
+
"@quenty/playerutils": "^8.1.0",
|
|
43
|
+
"@quenty/promise": "^10.1.0",
|
|
44
|
+
"@quenty/promisemaid": "^5.1.0",
|
|
45
|
+
"@quenty/receiptprocessing": "^7.1.0",
|
|
46
|
+
"@quenty/remoting": "^12.1.0",
|
|
47
|
+
"@quenty/rx": "^13.1.0",
|
|
48
|
+
"@quenty/rxbinderutils": "^14.1.0",
|
|
49
|
+
"@quenty/servicebag": "^11.1.0",
|
|
50
|
+
"@quenty/signal": "^7.1.0",
|
|
51
|
+
"@quenty/statestack": "^14.1.0",
|
|
52
52
|
"@quenty/string": "^3.1.0",
|
|
53
53
|
"@quenty/table": "^3.4.0",
|
|
54
|
-
"@quenty/valueobject": "^13.
|
|
54
|
+
"@quenty/valueobject": "^13.1.0"
|
|
55
55
|
},
|
|
56
|
-
"gitHead": "
|
|
56
|
+
"gitHead": "e0148dde5ca3864389a0faa2da66153a776acc1e"
|
|
57
57
|
}
|
|
@@ -187,7 +187,7 @@ end
|
|
|
187
187
|
Returns true if the prompt is open
|
|
188
188
|
|
|
189
189
|
@param player Player
|
|
190
|
-
@return boolean
|
|
190
|
+
@return Promise<boolean>
|
|
191
191
|
]=]
|
|
192
192
|
function GameProductServiceClient:PromisePlayerIsPromptOpen(player)
|
|
193
193
|
assert(typeof(player) == "Instance" and player:IsA("Player"), "Bad player")
|
|
@@ -197,6 +197,20 @@ function GameProductServiceClient:PromisePlayerIsPromptOpen(player)
|
|
|
197
197
|
return self._helper:PromisePlayerIsPromptOpen(player)
|
|
198
198
|
end
|
|
199
199
|
|
|
200
|
+
--[=[
|
|
201
|
+
Returns a promise that will resolve when all prompts are closed
|
|
202
|
+
|
|
203
|
+
@param player Player
|
|
204
|
+
@return Promise
|
|
205
|
+
]=]
|
|
206
|
+
function GameProductServiceClient:PromisePlayerPromptClosed(player)
|
|
207
|
+
assert(typeof(player) == "Instance" and player:IsA("Player"), "Bad player")
|
|
208
|
+
assert(self ~= GameProductServiceClient, "Use serviceBag")
|
|
209
|
+
assert(self._serviceBag, "Not initialized")
|
|
210
|
+
|
|
211
|
+
return self._helper:PromisePlayerPromptClosed(player)
|
|
212
|
+
end
|
|
213
|
+
|
|
200
214
|
--[=[
|
|
201
215
|
Checks if the asset is ownable and if it is, checks player ownership. Otherwise, it checks if the asset
|
|
202
216
|
has been purchased this session. If the asset has not been purchased this session it prompts the user to
|
|
@@ -128,6 +128,32 @@ function GameProductService:HasPlayerPurchasedThisSession(player, assetType, idO
|
|
|
128
128
|
return self._helper:HasPlayerPurchasedThisSession(player, assetType, idOrKey)
|
|
129
129
|
end
|
|
130
130
|
|
|
131
|
+
--[=[
|
|
132
|
+
Returns true if the prompt is open
|
|
133
|
+
|
|
134
|
+
@param player Player
|
|
135
|
+
@return Promise<boolean>
|
|
136
|
+
]=]
|
|
137
|
+
function GameProductService:PromisePlayerIsPromptOpen(player)
|
|
138
|
+
assert(typeof(player) == "Instance" and player:IsA("Player"), "Bad player")
|
|
139
|
+
assert(self._serviceBag, "Not initialized")
|
|
140
|
+
|
|
141
|
+
return self._helper:PromisePlayerIsPromptOpen(player)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
--[=[
|
|
145
|
+
Returns a promise that will resolve when all prompts are closed
|
|
146
|
+
|
|
147
|
+
@param player Player
|
|
148
|
+
@return Promise
|
|
149
|
+
]=]
|
|
150
|
+
function GameProductService:PromisePlayerPromptClosed(player)
|
|
151
|
+
assert(typeof(player) == "Instance" and player:IsA("Player"), "Bad player")
|
|
152
|
+
assert(self._serviceBag, "Not initialized")
|
|
153
|
+
|
|
154
|
+
return self._helper:PromisePlayerPromptClosed(player)
|
|
155
|
+
end
|
|
156
|
+
|
|
131
157
|
--[=[
|
|
132
158
|
Prompts the user to purchase the asset, and returns true if purchased
|
|
133
159
|
|
|
@@ -126,6 +126,22 @@ function GameProductServiceHelper:PromisePlayerIsPromptOpen(player)
|
|
|
126
126
|
return marketeer:IsPromptOpen()
|
|
127
127
|
end)
|
|
128
128
|
end
|
|
129
|
+
|
|
130
|
+
--[=[
|
|
131
|
+
Promises the player prompt as opened
|
|
132
|
+
|
|
133
|
+
@param player Player
|
|
134
|
+
@return Promise<boolean>
|
|
135
|
+
]=]
|
|
136
|
+
function GameProductServiceHelper:PromisePlayerPromptClosed(player)
|
|
137
|
+
assert(typeof(player) == "Instance" and player:IsA("Player"), "Bad player")
|
|
138
|
+
|
|
139
|
+
return self:_promisePlayerMarketeer(player)
|
|
140
|
+
:Then(function(marketeer)
|
|
141
|
+
return marketeer:PromisePlayerPromptClosed()
|
|
142
|
+
end)
|
|
143
|
+
end
|
|
144
|
+
|
|
129
145
|
--[=[
|
|
130
146
|
Observes player ownership
|
|
131
147
|
|
|
@@ -12,6 +12,7 @@ local Maid = require("Maid")
|
|
|
12
12
|
local Observable = require("Observable")
|
|
13
13
|
local Promise = require("Promise")
|
|
14
14
|
local Signal = require("Signal")
|
|
15
|
+
local ValueObject = require("ValueObject")
|
|
15
16
|
|
|
16
17
|
local PlayerAssetMarketTracker = setmetatable({}, BaseObject)
|
|
17
18
|
PlayerAssetMarketTracker.ClassName = "PlayerAssetMarketTracker"
|
|
@@ -38,23 +39,25 @@ function PlayerAssetMarketTracker.new(assetType, convertIds, observeIdsBrio)
|
|
|
38
39
|
self._purchasedThisSession = {} -- [number] = true
|
|
39
40
|
self._receiptProcessingExpected = false
|
|
40
41
|
|
|
41
|
-
self.
|
|
42
|
-
self.
|
|
43
|
-
self._maid:
|
|
44
|
-
|
|
45
|
-
self.Purchased = Signal.new() -- :Fire(id)
|
|
46
|
-
self._maid:GiveTask(self.Purchased)
|
|
47
|
-
|
|
48
|
-
self.PromptFinished = Signal.new() -- :Fire(id, isPurchased)
|
|
49
|
-
self._maid:GiveTask(self.PromptFinished)
|
|
50
|
-
|
|
51
|
-
self.ShowPromptRequested = Signal.new() -- :Fire(id)
|
|
52
|
-
self._maid:GiveTask(self.ShowPromptRequested)
|
|
42
|
+
self._promptsOpenCount = self._maid:Add(ValueObject.new(0, "number"))
|
|
43
|
+
self.Purchased = self._maid:Add(Signal.new()) -- :Fire(id)
|
|
44
|
+
self.PromptFinished = self._maid:Add(Signal.new()) -- :Fire(id, isPurchased)
|
|
45
|
+
self.ShowPromptRequested = self._maid:Add(Signal.new()) -- :Fire(id)
|
|
53
46
|
|
|
54
47
|
self._maid:GiveTask(self.Purchased:Connect(function(id)
|
|
55
48
|
self._purchasedThisSession[id] = true
|
|
56
49
|
end))
|
|
57
50
|
|
|
51
|
+
self._maid:GiveTask(self._promptsOpenCount:Observe():Subscribe(function(promptsOpen)
|
|
52
|
+
if promptsOpen <= 0 then
|
|
53
|
+
local promise = self._promiseNoPromptOpen
|
|
54
|
+
self._promiseNoPromptOpen = nil
|
|
55
|
+
if promise then
|
|
56
|
+
promise:Resolve()
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end))
|
|
60
|
+
|
|
58
61
|
self._maid:GiveTask(function()
|
|
59
62
|
while #self._pendingPurchasePromises > 0 do
|
|
60
63
|
local pending = table.remove(self._pendingPurchasePromises, #self._pendingPurchasePromises)
|
|
@@ -70,6 +73,10 @@ function PlayerAssetMarketTracker.new(assetType, convertIds, observeIdsBrio)
|
|
|
70
73
|
return self
|
|
71
74
|
end
|
|
72
75
|
|
|
76
|
+
function PlayerAssetMarketTracker:ObservePromptOpenCount()
|
|
77
|
+
return self._promptsOpenCount:Observe()
|
|
78
|
+
end
|
|
79
|
+
|
|
73
80
|
--[=[
|
|
74
81
|
Observes an asset purchased
|
|
75
82
|
|
|
@@ -142,7 +149,7 @@ function PlayerAssetMarketTracker:PromisePromptPurchase(idOrKey)
|
|
|
142
149
|
end
|
|
143
150
|
|
|
144
151
|
-- We reject here because there's no safe way to queue this
|
|
145
|
-
if self.
|
|
152
|
+
if self._promptsOpenCount.Value > 0 then
|
|
146
153
|
return Promise.rejected(string.format("[PlayerAssetMarketTracker] - Either already prompting user, or prompting is on cooldown. Will not prompt for %s", idOrKey))
|
|
147
154
|
end
|
|
148
155
|
|
|
@@ -161,12 +168,12 @@ function PlayerAssetMarketTracker:PromisePromptPurchase(idOrKey)
|
|
|
161
168
|
local promptOpenPromise = Promise.new()
|
|
162
169
|
self._pendingPromptOpenPromises[id] = promptOpenPromise
|
|
163
170
|
|
|
164
|
-
self.
|
|
171
|
+
self._promptsOpenCount.Value = self._promptsOpenCount.Value + 1
|
|
165
172
|
promptOpenPromise:Finally(function()
|
|
166
173
|
if self._pendingPromptOpenPromises[id] == promptOpenPromise then
|
|
167
174
|
self._pendingPromptOpenPromises[id] = nil
|
|
168
175
|
end
|
|
169
|
-
self.
|
|
176
|
+
self._promptsOpenCount.Value = self._promptsOpenCount.Value - 1
|
|
170
177
|
end)
|
|
171
178
|
end
|
|
172
179
|
|
|
@@ -225,7 +232,7 @@ end
|
|
|
225
232
|
@return boolean
|
|
226
233
|
]=]
|
|
227
234
|
function PlayerAssetMarketTracker:IsPromptOpen()
|
|
228
|
-
return self.
|
|
235
|
+
return self._promptsOpenCount.Value > 0
|
|
229
236
|
end
|
|
230
237
|
|
|
231
238
|
--[=[
|
|
@@ -14,6 +14,8 @@ local MarketplaceUtils = require("MarketplaceUtils")
|
|
|
14
14
|
local PlayerAssetOwnershipTracker = require("PlayerAssetOwnershipTracker")
|
|
15
15
|
local PlayerAssetMarketTracker = require("PlayerAssetMarketTracker")
|
|
16
16
|
local GameConfigAssetTypeUtils = require("GameConfigAssetTypeUtils")
|
|
17
|
+
local Promise = require("Promise")
|
|
18
|
+
local Rx = require("Rx")
|
|
17
19
|
|
|
18
20
|
local PlayerMarketeer = setmetatable({}, BaseObject)
|
|
19
21
|
PlayerMarketeer.ClassName = "PlayerMarketeer"
|
|
@@ -102,6 +104,46 @@ function PlayerMarketeer:IsPromptOpen()
|
|
|
102
104
|
return false
|
|
103
105
|
end
|
|
104
106
|
|
|
107
|
+
--[=[
|
|
108
|
+
Promises that no prompt is open
|
|
109
|
+
|
|
110
|
+
@return Promise
|
|
111
|
+
]=]
|
|
112
|
+
function PlayerMarketeer:PromisePlayerPromptClosed()
|
|
113
|
+
if not self:IsPromptOpen() then
|
|
114
|
+
return Promise.resolved()
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
if self._observeNextNoPromptOpen then
|
|
118
|
+
return Rx.toPromise(self._observeNextNoPromptOpen)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
local observeOpenCounts = {}
|
|
122
|
+
|
|
123
|
+
for assetType, assetTracker in pairs(self._assetMarketTrackers) do
|
|
124
|
+
observeOpenCounts[assetType] = assetTracker:ObservePromptOpenCount()
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
self._observeNextNoPromptOpen = Rx.combineLatest(observeOpenCounts):Pipe({
|
|
128
|
+
Rx.map(function(state)
|
|
129
|
+
for _, item in pairs(state) do
|
|
130
|
+
if item > 0 then
|
|
131
|
+
return false
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
return true
|
|
136
|
+
end);
|
|
137
|
+
Rx.where(function(value)
|
|
138
|
+
return value
|
|
139
|
+
end);
|
|
140
|
+
Rx.distinct();
|
|
141
|
+
Rx.share();
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
return Rx.toPromise(self._observeNextNoPromptOpen)
|
|
145
|
+
end
|
|
146
|
+
|
|
105
147
|
--[=[
|
|
106
148
|
Gets the current asset tracker
|
|
107
149
|
|