diff --git a/src/client/UI/CodesUI.lua b/src/client/UI/CodesUI.lua new file mode 100644 index 0000000..7ef8a13 --- /dev/null +++ b/src/client/UI/CodesUI.lua @@ -0,0 +1,35 @@ +local Players = game:GetService "Players" +local StarterPlayer = game:GetService "StarterPlayer" +local ReplicatedStorage = game:GetService "ReplicatedStorage" + +local Remotes = require(ReplicatedStorage.Common.Remotes) +local CentralUI = require(StarterPlayer.StarterPlayerScripts.Client.UI.CentralUI) + +local player = Players.LocalPlayer +local CodesUI = CentralUI.new(player.PlayerGui:WaitForChild "Codes") + +function CodesUI:_initialize() + player.PlayerGui:WaitForChild("MainUI").Codes.Activated:Connect(function() + self:setEnabled(not self._isOpen) + end) + + self._ui.LeftBackground.Confirm.Activated:Connect(function() + if self._debounce or #self._ui.LeftBackground.TextBox.Text == 0 then + return + end + self._debounce = true + Remotes.Client:Get("RedeemCode"):CallServerAsync(self._ui.LeftBackground.TextBox.Text):andThen(function(message) + self._ui.LeftBackground.TextBox.Text = message + end) + task.wait(1) + self._debounce = nil + end) +end + +function CodesUI:OnOpen() + self._ui.LeftBackground.TextBox.Text = "" +end + +task.spawn(CodesUI._initialize, CodesUI) + +return CodesUI diff --git a/src/client/UI/init.lua b/src/client/UI/init.lua index 2a25036..4b5481c 100644 --- a/src/client/UI/init.lua +++ b/src/client/UI/init.lua @@ -3,10 +3,10 @@ require(script.Combat) require(script.Displays) require(script.Missions) require(script.BuffsUI) +require(script.CodesUI) require(script.SettingsUI) require(script.FixRichText) require(script.Inventories) require(script.ChestTimerUI) require(script.BillboardShops) - return 0 diff --git a/src/server/PlayerManager/ProfileTemplate.lua b/src/server/PlayerManager/ProfileTemplate.lua index 416c123..4de4777 100644 --- a/src/server/PlayerManager/ProfileTemplate.lua +++ b/src/server/PlayerManager/ProfileTemplate.lua @@ -38,6 +38,7 @@ return { PurchasedTeleporters = {}, PurchasedBoosts = {}, ActiveBoosts = {}, + RedeemedCodes = {}, }, PetData = { diff --git a/src/server/PurchaseManager/Chests.lua b/src/server/PurchaseManager/ChestsAndCodes.lua similarity index 54% rename from src/server/PurchaseManager/Chests.lua rename to src/server/PurchaseManager/ChestsAndCodes.lua index 50e4529..de2306f 100644 --- a/src/server/PurchaseManager/Chests.lua +++ b/src/server/PurchaseManager/ChestsAndCodes.lua @@ -1,3 +1,5 @@ +local ServerStorage = game:GetService "ServerStorage" +local DataStoreService = game:GetService "DataStoreService" local ReplicatedStorage = game:GetService "ReplicatedStorage" local ServerScriptService = game:GetService "ServerScriptService" @@ -10,65 +12,43 @@ local zoneUtils = require(ReplicatedStorage.Common.Utils.ZoneUtils) local clockUtils = require(ReplicatedStorage.Common.Utils.ClockUtils) local productRewarders = require(ServerScriptService.Server.PurchaseManager.DeveloperProducts.Products.Rewarders) +local codes = ServerStorage.Codes local packIDs = ReplicatedStorage.Config.DevProductData.Packs -local chestTimerLength = ReplicatedStorage.Config.DevProductData.Chests.ChestTimerLength.Value + +local success, expiredCodes +local expiredCodeDataStore = DataStoreService:GetDataStore "ExpiredCodes" local VIPChestZone = Zone.new(zoneUtils.getTaggedForZone "VIPChestHitbox") local groupChestZone = Zone.new(zoneUtils.getTaggedForZone "GroupChestHitbox") +local chestTimerLength = ReplicatedStorage.Config.DevProductData.Chests.ChestTimerLength.Value VIPChestZone:relocate() groupChestZone:relocate() --- Award order for group chests: GemBoost > TinyFearPack > DamageBoost > FearBoost > SmallGemPack > FearlessBoost --- Award order for vip chests: FearBoost > SmallGemPack > DamageBoost > GemBoost > TinyFearPack > FearlessBoost +local function awardItem(player, item) + if item:match "Boost" then + Remotes.Server:Get("OpenRobuxShopOnClient"):SendToPlayer(player, "Boosts") + store:dispatch(actions.incrementPlayerBoostCount(player.Name, item)) + else + productRewarders[packIDs:FindFirstChild(item, true).Value](player) + end +end local vipChestAwards = { - function(player) - Remotes.Server:Get("OpenRobuxShopOnClient"):SendToPlayer(player, "Boosts") - store:dispatch(actions.incrementPlayerBoostCount(player.Name, "FearBoost15")) - end, - function(player) - productRewarders[packIDs.Gems["SmallGemPack"].Value](player) - end, - function(player) - Remotes.Server:Get("OpenRobuxShopOnClient"):SendToPlayer(player, "Boosts") - store:dispatch(actions.incrementPlayerBoostCount(player.Name, "DamageBoost15")) - end, - function(player) - Remotes.Server:Get("OpenRobuxShopOnClient"):SendToPlayer(player, "Boosts") - store:dispatch(actions.incrementPlayerBoostCount(player.Name, "GemsBoost15")) - end, - function(player) - productRewarders[packIDs.Fear["TinyFearPack"].Value](player) - end, - function(player) - Remotes.Server:Get("OpenRobuxShopOnClient"):SendToPlayer(player, "Boosts") - store:dispatch(actions.incrementPlayerBoostCount(player.Name, "FearlessBoost15")) - end, + "FearBoost", + "SmallGemPack", + "DamageBoost", + "GemsBoost", + "TinyFearPack", + "FearlessBoost", } local groupChestAwards = { - function(player) - Remotes.Server:Get("OpenRobuxShopOnClient"):SendToPlayer(player, "Boosts") - store:dispatch(actions.incrementPlayerBoostCount(player.Name, "GemsBoost15")) - end, - function(player) - productRewarders[packIDs.Fear["TinyFearPack"].Value](player) - end, - function(player) - Remotes.Server:Get("OpenRobuxShopOnClient"):SendToPlayer(player, "Boosts") - store:dispatch(actions.incrementPlayerBoostCount(player.Name, "DamageBoost15")) - end, - function(player) - Remotes.Server:Get("OpenRobuxShopOnClient"):SendToPlayer(player, "Boosts") - store:dispatch(actions.incrementPlayerBoostCount(player.Name, "FearBoost15")) - end, - function(player) - productRewarders[packIDs.Gems["SmallGemPack"].Value](player) - end, - function(player) - Remotes.Server:Get("OpenRobuxShopOnClient"):SendToPlayer(player, "Boosts") - store:dispatch(actions.incrementPlayerBoostCount(player.Name, "FearlessBoost15")) - end, + "GemsBoost", + "TinyFearPack", + "DamageBoost", + "FearBoost", + "SmallGemPack", + "FearlessBoost", } VIPChestZone.playerEntered:Connect(function(player) @@ -79,7 +59,7 @@ VIPChestZone.playerEntered:Connect(function(player) if not clockUtils.hasTimeLeft(chestTimers.VIPChest, chestTimerLength) then local currentAwardIndex = selectors.getStat(store:getState(), player.Name, "VIPChestAwardIndex") store:dispatch(actions.startChestTimer(player.Name, "VIPChest")) - vipChestAwards[currentAwardIndex](player) + awardItem(player, vipChestAwards[currentAwardIndex]) store:dispatch( actions.setPlayerStat(player.Name, "VIPChestAwardIndex", (currentAwardIndex % #vipChestAwards) + 1) ) @@ -94,11 +74,57 @@ groupChestZone.playerEntered:Connect(function(player) if not clockUtils.hasTimeLeft(chestTimers.GroupChest, chestTimerLength) then store:dispatch(actions.startChestTimer(player.Name, "GroupChest")) local currentAwardIndex = selectors.getStat(store:getState(), player.Name, "GroupChestAwardIndex") - groupChestAwards[currentAwardIndex](player) + awardItem(player, groupChestAwards[currentAwardIndex]) store:dispatch( actions.setPlayerStat(player.Name, "GroupChestAwardIndex", (currentAwardIndex % #groupChestAwards) + 1) ) end end) +Remotes.Server:Get("RedeemCode"):SetCallback(function(player, code) + local codeReward = if codes:FindFirstChild(code) then codes[code].Value else nil + if not codeReward then + if expiredCodes and expiredCodes[code] then + return "Code expired" + end + return "Invalid code" + end + if not selectors.hasRedeemedCode(store:getState(), player.Name, code) then + awardItem(player, codeReward) + store:dispatch(actions.redeemCode(player.Name, code)) + return "Code redeemed" + end + return "Code already redeemed" +end) + +task.spawn(function() + success, expiredCodes = pcall(expiredCodeDataStore.GetAsync, expiredCodeDataStore, "ExpiredCodes") + while not success do + task.wait(30) + success, expiredCodes = pcall(expiredCodeDataStore.GetAsync, expiredCodeDataStore, "ExpiredCodes") + end + + local foundMissingCode = false + if expiredCodes then + for _, code in codes:GetChildren() do + if not expiredCodes[code.Name] then + expiredCodes[code.Name] = code.Value + foundMissingCode = true + end + end + else + foundMissingCode = true + end + + if not foundMissingCode then + return + end + + success = pcall(expiredCodeDataStore.SetAsync, expiredCodeDataStore, "ExpiredCodes", expiredCodes) + while not success do + task.wait(30) + success = pcall(expiredCodeDataStore.SetAsync, expiredCodeDataStore, "ExpiredCodes", expiredCodes) + end +end) + return 0 diff --git a/src/server/PurchaseManager/Codes.lua b/src/server/PurchaseManager/Codes.lua deleted file mode 100644 index 61c7ebf..0000000 --- a/src/server/PurchaseManager/Codes.lua +++ /dev/null @@ -1 +0,0 @@ -return 0 diff --git a/src/server/State/Actions/ProductActions.lua b/src/server/State/Actions/ProductActions.lua index ed8556c..4fb8574 100644 --- a/src/server/State/Actions/ProductActions.lua +++ b/src/server/State/Actions/ProductActions.lua @@ -28,4 +28,11 @@ return { shouldSave = true, } end), + redeemCode = makeActionCreator("redeemCode", function(playerName: string, code: string) + return { + playerName = playerName, + code = code, + shouldSave = true, + } + end), } diff --git a/src/shared/State/DefaultStates.lua b/src/shared/State/DefaultStates.lua index 1156c1d..f11490c 100644 --- a/src/shared/State/DefaultStates.lua +++ b/src/shared/State/DefaultStates.lua @@ -54,6 +54,7 @@ return { PurchasedTeleporters = {}, PurchasedBoosts = {}, ActiveBoosts = {}, + RedeemedCodes = {}, }, MissionData = { diff --git a/src/shared/State/Reducer/PurchaseData.lua b/src/shared/State/Reducer/PurchaseData.lua index 20f2759..30be17e 100644 --- a/src/shared/State/Reducer/PurchaseData.lua +++ b/src/shared/State/Reducer/PurchaseData.lua @@ -60,4 +60,9 @@ return Rodux.createReducer({}, { draft[action.playerName].ActiveBoosts[action.boostName:match "%D+"] = nil end) end, + redeemCode = function(state, action) + return produce(state, function(draft) + draft[action.playerName].RedeemedCodes[action.code] = true + end) + end, }) diff --git a/src/shared/State/selectors.lua b/src/shared/State/selectors.lua index 3908d05..162e732 100644 --- a/src/shared/State/selectors.lua +++ b/src/shared/State/selectors.lua @@ -99,4 +99,7 @@ return { getChestTimers = function(state, playerName) return state.ChestTimers[playerName] end, + hasRedeemedCode = function(state, playerName, code) + return state.PurchaseData[playerName].RedeemedCodes[code] + end, }