Creating a twitter code system for in-game currency

This tutorial will be teaching you how to create a twitter code system that can be updated and used instantly without having to update the game version by migrating to latest update with just a module script to store all the twitter codes to be redeemed, the script to handle the redemption of the codes and a local script for the player to redeem to code to get in game currency.

Creating the module script to store codes

First create a module script and name it MainModule so that it can be required
You would see the default

local module = {}

return module

So within the table put the data using the format below

local module = {
    Test = {
        Expired = false,
        Currency = 100
    },
    Code1 = {
        Expired = false,
        Currency = 100
    }
}

return module

After that, publish the module by saving it to Roblox and copy lock it.

That’s all for the module script. You can increase the number of codes so that your players can redeem more codes and also remove codes and make codes expire at the same time.

If you update the module, just update the old module you used by overwriting the old module you used.

Ensure that you have the following remote events under Replicated Storage
Script - Roblox Studio 14_12_2020 5_18_34 pm

Server Script to handle redemption of codes

Now this part of the script would depend on the datastore you use. For this tutorial, I would be using the normal Roblox Datastore.

First, get all the services we are going to use.

local RS = game:GetService("ReplicatedStorage") --Remote Events
local DS = game:GetService("DataStoreService") -- Save Data
local IS = game:GetService("InsertService") --Load Module

For this tutorial, I would be my own module: Asset id 6085696646

We will set up some main variables

local ModuleId = 6085696646

local CodeDS = DS:GetDataStore("Codes")
local CashDS = DS:GetDataStore("Cash")

Now let’s set up some local functions

local function Fire(plr, ty)  --[[To tell players about how 
the redemption of code went.
plr is player instance
ty is to tell the local script what to show.
]]
    RS.RemoteEvents.Redeem.Status:FireClient(plr, ty)
end

This will tell the player if the redemption of the code was a success

local function Redeem(plr, Code, CodeTable)
--[[
This is to do the redemption of the code
plr is the player instance
Code is the code that the player entered
CodeTable is your module script dictionary that we have created
]]
    local done = false
    local CData

--Lets say we use leaderstats to save the data
    local Cash = plr.leaderstats.Cash.Value

    local success, Error = pcall(function()
        CData = CashDS:GetAsync(plr.UserId)
    end

    if success then
        for i,v in pairs(CodeTable) do
            if Code == i then
                Cash = Cash + v["Currency"]
                done = true
            end
        end
    else
        warn(Error)
    end

    return done
end

This will find the amount that the player would receive if they redeemed a certain code and add it

function CheckRedeemed(PlrData, Code)
--[[
This will be to check if the player has redeemed the code before
PlrData is a dictionary of all the codes the player redeemed
Code is the code that the player wants to redeem
]]
	local Not = true	
	for i,v in pairs(PlrData) do
		if Code == v then
			Not = false
		end
	end
	return Not
end

This will check if the player has redeemed the code before

Now lets get onto the main part of the code

First get the latest addition of the module. We would be loading and deleting the module everytime a player redeems a code so that the codes that can be redeemed will always be up to date and there will be no need for the newest game version to only use new updated twitter codes.

RS.RemoteEvents.Redeem.RedeemCheck.OnServerEvent:Connect(function(plr, code)
	local ModuleV = IS:GetLatestAssetVersionAsync(moduleId)
	
	local LoadModule = IS:LoadAssetVersion(ModuleV)
	
	local TableC = require(LoadModule:WaitForChild("MainModule"))
	print(TableC)
	
	LoadModule:Destroy()

Now we will get all the codes the player redeemed before

    local Data
	
	local success, Error = pcall(function()
		Data = CodeDS:GetAsync(plr.UserId)
	end)
	
	if success then
		if Data then
		else
--If no data then set up an empty dictionary
			Data = {}
		end
	else
		warn(Error)
		Fire(plr, 5)
		return
	end

And for the rest of the checks

    if CheckRedeemed(Data, code) then
	--Check if code has been redeemed
		if TableC[code] then
--Check if code exists
			if not TableC[code]["Expired"] then
--Check if code has not expired yet
				if Redeem(plr, code, TableC) then
--Check if the redemption was a success
					Data[#Data + 1] = code
--Add the code the player just redeemed to the dictionary of codes redeemed
					local good, bad = pcall(function()
						CodeDS:UpdateAsync(plr.UserId, function(old)
							return Data
						end)
					end)
					--Save the dictionary of codes redeemed
					Fire(plr, 0)
				else
					Fire(plr, 2)
				end
			else
				Fire(plr, 4)
			end
		else
			if code ~= "" then
--Check if any characters were input by the player
				Fire(plr, 3)
			else
				Fire(plr, 6)
			end
		end
	else
		Fire(plr, 1)
	end
end)

Now for how the player would redeem the code

Lets have a simple code redemption area
Platform Universe Beta - Roblox Studio 14_12_2020 5_11_45 pm

With the following children
Platform Universe Beta - Roblox Studio 14_12_2020 5_13_52 pm
And a simple gui

So for the script under the click detector, you want it to show the player the redemption gui

local RS = game:GetService("ReplicatedStorage")

script.Parent.MouseClick:Connect(function(plr)
	RS.RemoteEvents.Redeem.Gui:FireClient(plr)
end)

For the local script to tell the server what code the player entered, to tell the player if the redemption was a success and to close the gui

So the gui children are
Script - Roblox Studio 14_12_2020 5_22_19 pm

So for the local script

Get the Services needed

local RS = game:GetService("ReplicatedStorage")

Receive the remote event when fired from the click detector script

RS.RemoteEvents.Redeem.Gui.OnClientEvent:Connect(function()
	script.Parent.Visible = true
end)

To tell the server what code the player wants to redeem

script.Parent.TextButton.MouseButton1Click:Connect(function()
	RS.RemoteEvents.Redeem.RedeemCheck:FireServer(script.Parent.TextBox.Text)
end)

Function to reset the textbox

local function BoxColour()
	script.Parent.TextBox.BackgroundColor3 = Color3.fromRGB(165, 165, 165)
	script.Parent.TextBox.Text = ""
end

Closing the gui

script.Parent.Close.MouseButton1Click:Connect(function()
	script.Parent.Visible = false
end)

Now to tell the player if the redemption of the code was a success!Sorry, but this part is a bit messy.

RS.RemoteEvents.Redeem.Status.OnClientEvent:Connect(function(info)
	local Responses = {
		[1] = "Code already redeemed",
		[2] = "Error in redeeming code",
		[3] = "Invalid Code",
		[4] = "Code Expired",
		[5] = "Data not found",
		[6] = "Enter a code"
	}
	if info == 0 then
		script.Parent.TextBox.BackgroundColor3 = Color3.fromRGB(0, 255, 127)
		script.Parent.TextBox.Text = "Code redeemed!"
		wait(3)
		BoxColour()
	else
		script.Parent.TextBox.BackgroundColor3 = Color3.fromRGB(255, 0, 0)
		script.Parent.TextBox.Text = Responses[info]
		wait(3)
		BoxColour()
	end
end)
All the full codes
Module
local module = {
    Test = {
        Expired = false,
        Currency = 100
    },
    Code1 = {
        Expired = false,
        Currency = 100
    }
}

return module
Server Script
local RS = game:GetService("ReplicatedStorage")
local DS = game:GetService("DataStoreService")
local IS = game:GetService("InsertService")

local DS2 = require(game.ServerScriptService.DataFolder.DataStore2)

DS2.Combine("MainData", "Tokens")

local moduleId = 6085696646

local CodeDS = DS:GetDataStore("Codes")
local CashDS = DS:GetDataStore("Cash")

local function Fire(plr, ty)
	RS.RemoteEvents.Redeem.Status:FireClient(plr, ty)
end

local function Redeem(plr, Code, CodeTable)
--[[
This is to do the redemption of the code
plr is the player instance
Code is the code that the player entered
CodeTable is your module script dictionary that we have created
]]
    local done = false
    local CData

--Lets say we use leaderstats to save the data
    local Cash = plr.leaderstats.Cash.Value

    local success, Error = pcall(function()
        CData = CashDS:GetAsync(plr.UserId)
    end

    if success then
        for i,v in pairs(CodeTable) do
            if Code == i then
                Cash = Cash + v["Currency"]
            end
        end
    else
        warn(Error)
    end

    return done
end

function CheckRedeemed(PlrData, Code)
	local Not = true	
	for i,v in pairs(PlrData) do
		if Code == v then
			Not = false
		end
	end
	return Not
end

RS.RemoteEvents.Redeem.RedeemCheck.OnServerEvent:Connect(function(plr, code)
	local ModuleV = IS:GetLatestAssetVersionAsync(moduleId)
	
	local LoadModule = IS:LoadAssetVersion(ModuleV)
	
	local TableC = require(LoadModule:WaitForChild("MainModule"))
	print(TableC)
	
	LoadModule:Destroy()
	local Data
	
	local success, Error = pcall(function()
		Data = CodeDS:GetAsync(plr.UserId)
	end)
	
	if success then
		if Data then
		else
			Data = {
				
			}
		end
	else
		warn(Error)
		Fire(plr, 5)
	end
	
	if CheckRedeemed(Data, code) then
	
		if TableC[code] then
			if not TableC[code]["Expired"] then
				if Redeem(plr, code, TableC) then
					Data[#Data + 1] = code
					local good, bad = pcall(function()
						CodeDS:UpdateAsync(plr.UserId, function(old)
							return Data
						end)
					end)
					
					Fire(plr, 0)
				else
					Fire(plr, 2)
				end
			else
				Fire(plr, 4)
			end
		else
			if code ~= "" then
				Fire(plr, 3)
			else
				Fire(plr, 6)
			end
		end
	else
		Fire(plr, 1)
	end
end)
Click Detector
local RS = game:GetService("ReplicatedStorage")

script.Parent.MouseClick:Connect(function(plr)
	RS.RemoteEvents.Redeem.Gui:FireClient(plr)
end)
Local Script
local RS = game:GetService("ReplicatedStorage")

RS.RemoteEvents.Redeem.Gui.OnClientEvent:Connect(function()
	script.Parent.Visible = true
end)

script.Parent.TextButton.MouseButton1Click:Connect(function()
	RS.RemoteEvents.Redeem.RedeemCheck:FireServer(script.Parent.TextBox.Text)
end)

local function BoxColour()
	script.Parent.TextBox.BackgroundColor3 = Color3.fromRGB(165, 165, 165)
	script.Parent.TextBox.Text = ""
end

RS.RemoteEvents.Redeem.Status.OnClientEvent:Connect(function(info)
	local Responses = {
		[1] = "Code already redeemed",
		[2] = "Error in redeeming code",
		[3] = "Invalid Code",
		[4] = "Code Expired",
		[5] = "Data not found",
		[6] = "Enter a code"
	}
	if info == 0 then
		script.Parent.TextBox.BackgroundColor3 = Color3.fromRGB(0, 255, 127)
		script.Parent.TextBox.Text = "Code redeemed!"
		wait(3)
		BoxColour()
	else
		script.Parent.TextBox.BackgroundColor3 = Color3.fromRGB(255, 0, 0)
		script.Parent.TextBox.Text = Responses[info]
		wait(3)
		BoxColour()
	end
end)
end)

script.Parent.Close.MouseButton1Click:Connect(function()
	script.Parent.Visible = false
end)

So there you go, create a system to redeem codes only by updating the module storing the code and not needing to update the entire game.

Note: Since only the updating of module is required to update the twitter codes, you can store the module on another place

This is my first tutorial, any feedback and criticism to improve would be greatly appreciated!

32 Likes

There seems to be a lot of duplicated code, for instance every assignment to BackgroundColor3 is to the same color except the first one. I would also do, instead of returning arbitrary error code numbers, just return the error message itself.

RS.RemoteEvents.Redeem.Status.OnClientEvent:Connect(function(info)
	-- info would be a table
	script.Parent.TextBox.BackgroundColor3 = info.ResponseColor
	script.Parent.TextBox.Text = info.Message
	wait(3)
	BoxColour()
end)

And just like that, no duplicated code.

5 Likes

Ah yes, I was referring to the code I created, and was quite messy. I would edit the main post for that.

Edit: Made the corrections

3 Likes

Very interesting, GUIS can be actually more improved for example use TweenService and tween the GUI instead of setting the frame visible to false and true

You should make the GUIS more user-friendly such that there are more designs like for example a Twitter Icon? As such, the player would be able to know where to redeem to code from

Anyway, you did quite well overall :slight_smile:

3 Likes

As such, the tutorial was more focused on scripting than GUIs. Overall nice!

Some feedback :smiley:

Personally I would use a remote event and store the responses on the datastore with the code information, that way I always have the option to create custom messages, I personally would use a remote function instead.

Server

local codeFunction = game:GetService("ReplicatedStorage").CodeFolder.Redeem

codeFunction.OnServerInvoke = function(player, code)
   local success, datastoreError, canRedeem, customMessage = checkCode(code)
   if success then
      if canRedeem then 
         -- Redeem Stuff Code Here
         return customMessage or "Redeemed!"
      else
         return "Code can't be redeemed at this moment"
      end
   else
      if datastoreError then
          return "Roblox Datastore Error (Try again in a little while!)"
      end
      return "Code Does Not Exist"
   end
end
4 Likes

Oh, I have yet to learn remote functions, and I will take in your feedback.
But

Shouldn’t this be

elseif DataStore Error then
	return "Roblox DataStore Error (Try again in a little while)" 
else
	return "Code Does Not Exist"
end

Your code would be returning both the DataStore error and the code does not exist.

Also I thought that you could already create custom messages here

Care to clarify?

This would work as well, the way I wrote it was just me trying to save myself from typing a bit. But they would both work since we are using return within a function. One you return a value it ends the thread, so it doesn’t go on to the other one.

Once you return it ends the function, you can’t return twice.

You can, however, it means I would need to update my game everytime I want a new custom message for a code. So yeah storing it in the datastore would be easier and not require updates

1 Like

Thanks for the clarification.

This is especially important because I wanted the codes to be updated without updating the entire game.

Thanks! :slight_smile:

1 Like

I made something similar without noticing to your number thing, and now I see a post similar to my codes Module LOL WHAT is going OONNN

Bruh sad you made it legit 1 day before I did, I hate this.

I DIDNT ever Look at your module, and even someone copied mine so LOL WHAT this is crazy

I think there might be like a trend for new developers to make modules like these…? Idk?

1 Like

how to do the main module thing require(Load:WaitForChild(“MainModule”)) ←

RS.RemoteEvents.Redeem.RedeemCheck.OnServerEvent:Connect(function(plr, code)
	local ModuleV = IS:GetLatestAssetVersionAsync(moduleId)
	
	local LoadModule = IS:LoadAssetVersion(ModuleV)
	
	local TableC = require(LoadModule:WaitForChild("MainModule"))            
	print(TableC)
	
	LoadModule:Destroy()

Is there any reason you’re using module to store code information? It seems unnecessary and a table inside the script would be way faster than yielding and waiting for the module to be required.

The module is so that there is no need for all your game servers to be the latest version of your game to use the code. If say you store your codes in a table inside the script, then if you add new codes or remove them, players in the previous game server version will not be able to use the new codes or are still able to use the old codes you removed.

Correct me if I misunderstood, but you’re saying it automatically updates?

Modules are required once. When the server first starts up and requires the module, it won’t automatically require the module again if the module is updated with a new set of codes.

Modules don’t automatically in-game/in older server versions update like you’re suggesting.

Yes it does.

Partly correct. Everytime the function is called, it would load the module, require it and get the data, and then destroy it so that it would not spam.
It can be seen by

1 Like

Is it returning any errors? Does the dictionary get printed? What is wrong with it?

infinite yield error on the (“main module”) string

Oh, its just an error from WaitForChild
You could just do

require(LoadModule:WaitForChild("MainModule", math.huge)) 

to get rid of the error.