Feedback on my Key System

Key System [V0.1]

I’ve been working on a key system for the past 2 and a half weeks and I wanted to show off my hard work. I used this as a way to experiment with data stores and how to better optimize data stores. I plan to add many more features with time which can be found listed below. Any criticism or feedback on how I can improve this project is greatly appreciated and hopefully open this project up to the public after improving it.

Concept

Initially, I took inspiration from to.yhouse, a website for artists, key system. Toyhouse doesn’t allow users to interact or create content on their website unless they have an account. To create an account on Toyhouse, you must have an auto-generated key. These keys can be provided and generated by other users of the site and owners. This inspired me to take my own twist on it.

My key system restricts players who do not have access to the game from viewing the contents within the game. A key, a randomly generated 12-character code, can be used to gain access to the game’s contents. Keys can also be generated by players by playing the game (not yet implemented), once this key is given to another player and used, the owner of the key is given rewards upon rejoining (30 Coins).

Future Features

  • Players can generate keys through actions and playing the game
  • Inventory / non-stat rewards
  • Ability to list all existing keys
  • And more…

How It All Works

Modules scripts were used to better organize most of the code, although still messy.

image

image

KeyServerManager handles the generation and management of keys. It saves and loads any current keys that are saved within the game and removes any that have been used. It also handles rewarding players who have shared their respective generated keys with others.

KeyServerManager
local RS = game:GetService("ReplicatedStorage")
local players = game:GetService("Players")

local Main = require(script.Main)
local Rewards = require(script.Main.Rewards)

local InsertKeyEvent = RS:FindFirstChild("InsertKey")
local GenerateKeyEvent = RS:FindFirstChild("GenerateKey")
local RemoveAccess = RS:FindFirstChild("RemovePlayer")

InsertKeyEvent.OnServerEvent:Connect(Main.UseKey)
GenerateKeyEvent.OnServerEvent:Connect(Main.GenerateNewKey)
RS.test.OnServerEvent:Connect(Rewards.SetReward)
RemoveAccess.OnServerEvent:Connect(Main.RemoveAccess)

players.PlayerAdded:Connect(Main.onPlayerAdded)
Main
local KeyServer = {}

local RewardsModule = require(script.Rewards)

local DSS = game:GetService("DataStoreService")
local RS = game:GetService("ReplicatedStorage")

local GenerateKey = RS.GenerateKey

local KeyStorage = DSS:GetDataStore("KeyStorage")
local PlayerData = DSS:GetDataStore("KeyStorage", "Players")

local function GenerateRandomKey(plr)
	local characters = {"a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","1","2","3","4","5","6","7","8","9","0"}
	local KeyLength = 12
	local Max = #characters
	local NewKey = ""

	for i = 1, KeyLength do
		local RandomCharacter = characters[math.random(1,Max)]
		NewKey = NewKey..tostring(RandomCharacter)
	end

	return NewKey
end

KeyServer.UseKey = function(player, TargetKey)
	local success, owner = pcall(function()
		return KeyStorage:GetAsync(TargetKey)
	end)

	if success then
		if owner ~= nil and owner ~= "" then 
			print(owner)
			RewardsModule.SetReward(owner) 
		end
		
		KeyStorage:RemoveAsync(TargetKey)	
		PlayerData:SetAsync(player.UserId, TargetKey)

		print("User: "..player.Name, "Key Used: "..TargetKey)

		player.PlayerGui.KeyGUI:Destroy() 

		return true
	else
		return false
	end
end

KeyServer.GenerateNewKey = function(sender,plr)
	local key = GenerateRandomKey() --creates a random key

	local success, err = pcall(function()
		if plr then
			KeyStorage:SetAsync(key, plr.UserId) --Stores the key as available and the player that generated the key
		else
			KeyStorage:SetAsync(key, "") --Stores the key as available without a player origin
		end
	end)
	
	if success then
		GenerateKey:FireClient(sender, tostring(key))
		return key 
	else 
		warn("Failed to generate key: "..err)
		return nil
	end
end

KeyServer.onPlayerAdded = function(player)
	local success, usedkey = pcall(function()
		return PlayerData:GetAsync(player.UserId)
	end)
	
	RewardsModule.OnPlayerAdded(player)
	
	if success and usedkey then
		player.CharacterAdded:Wait()
		player.PlayerGui.KeyGUI:Destroy() 
		return
	end
end

KeyServer.RemoveAccess = function(player)
	local success, usedkey = pcall(function()
		return PlayerData:GetAsync(player.UserId)
	end)
	
	if success and usedkey then
		PlayerData:RemoveAsync(player.UserId)
		player:Kick("You may rejoin, but you'll need to provide a new key to regain access.")
	end
end

return KeyServer
Rewards
local RewardMod = {}

local DSS = game:GetService("DataStoreService")
local Players = game:GetService("Players")

local KeyStorage = DSS:GetDataStore("KeyStorage")
local PlayerRewards = DSS:GetDataStore("KeyStorage", "RewardData")

local LootTable = require(script.Parent.LootTable)

local Items = {"Rock","Stick","Gun"}
local PlayerStats = {"XP","Coins"}

local function GetRandomReward()
	local Total = 0
	
	for _, item in pairs(LootTable) do
		Total = Total + item.Weight
	end
	
	local RandomVal = math.random(1, Total)
	local Current = 0
	
	for _, item in pairs(LootTable) do
		Current = item.Weight
		
		if Current >= RandomVal then
			if item.Amount then
				return item.Name, item.Amount 
			else 
				return item.Name, 1
			end 
		end
	end
end

local function FindPlayer(Target)
	if type(Target) == "number" then Target = game:GetService("Players"):GetPlayerByUserId(Target)end 
	for _, player in pairs(Players:GetChildren()) do --Checks all players to see if the owner of the key is in the current server
		if player == Target then
			return player
		end
	end
	
	return nil
end

local function AddStatItem(player, item, amount)
	local TargetStat = player.PlayerData:FindFirstChild(item)
	TargetStat.Value += amount
end

local function AddInventoryItem(player, item, amount)
	local Inventory = player:FindFirstChild("Inventory")
	
	if Inventory:FindFirstChild(item) then
		local item = Inventory:FindFirstChild(item)
		item.Value += tonumber(amount)
	else
		local NewItem = Instance.new("IntValue")
		NewItem.Name = item
		NewItem.Value = amount
	end
end

RewardMod.SetReward = function(playerId, key)
	--redefines the player variable in case the player's Id is sent rather than the player instance.
	
	local FoundPlayer = FindPlayer(playerId) --if the player is in the server
	
	local Reward, Amount = GetRandomReward() --Obtains a random reward
	local IsRewardStat = table.find(PlayerStats, Reward) --checks if its a stat reward
	local IsRewardItem = table.find(Items, Reward) --checks if its an item reward
	
	if FoundPlayer ~= nil then
		if IsRewardStat then --Checks if the reward is a stat
			AddStatItem(FoundPlayer, Reward, Amount)
			print(Reward, Amount)
			return
		elseif IsRewardItem then --or an item
			AddInventoryItem(FoundPlayer, Reward, Amount)
			print(Reward, Amount)
			return
		end
	else
		local success, err = pcall(function()
			return PlayerRewards:SetAsync(playerId, {Name = Reward, Amount = Amount})  
		end)
		
		if success then
			print("Successfully saved!")
		else
			warn(err)
		end
	end
end

RewardMod.OnPlayerAdded = function(player)
	local reward
	
	print(player.UserId)
	
	local success, err = pcall(function()
		reward = PlayerRewards:GetAsync(player)
		return reward
	end) 
	
	local IsRewardStat = table.find(PlayerStats, reward) --checks if its a stat reward
	local IsRewardItem = table.find(Items, reward) --checks if its an item reward
	
	local player = game.Players:GetPlayerByUserId(player.UserId)
	
	if reward then
		if success then
			local RewardName = reward.Name
			local Amount = reward.Amount

			if IsRewardStat then
				AddStatItem(player, RewardName, Amount)
			elseif IsRewardItem then
				AddStatItem(player, RewardName, Amount)
			end
			print(RewardName, Amount)

			PlayerRewards:RemoveAsync(player.UserId)
		else
			warn(err)
		end
	end
end

return RewardMod
LootTable
local LootTable = {
	--{Name = Item Name, Weight = #, Amount (optional)};
	{Name = "Coins", Weight = 100, Amount = 30};
	{Name = "Coins", Weight = 1, Amount = 50};
}

PlayerDataServer handles the creation and data of the player’s inventory and currency. Nothing too special.

PlayerDataServer
local MainMod = require(script.Main)

local RS = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")

local AddEvent = RS.AddCoins
local SubtractEvent = RS.SubtractCoins

Players.PlayerAdded:Connect(MainMod.OnPlayerAdded)
Players.PlayerRemoving:Connect(MainMod.PlayerRemoving)

AddEvent.OnServerEvent:Connect(MainMod.AddCoins)
SubtractEvent.OnServerEvent:Connect(MainMod.SubtractCoins)
Main
local PlayerMod = {}

local DSS = game:GetService("DataStoreService")

local PlayerDatastore = DSS:GetDataStore("PlayerData")
local StatsDatastore =  DSS:GetDataStore("PlayerData", "Coins")
local InvDatastore =  DSS:GetDataStore("PlayerData", "Inventory")

PlayerMod.OnPlayerAdded = function(plr)
	local PlayerFolder = Instance.new("Folder")
	PlayerFolder.Name = "PlayerData"
	PlayerFolder.Parent = plr
	
	local InventoryFolder = Instance.new("Folder")
	InventoryFolder.Name = "Inventory"
	InventoryFolder.Parent = PlayerFolder
	
	local Coins = Instance.new("IntValue")
	Coins.Name = "Coins"
	Coins.Value = 0
	Coins.Parent = PlayerFolder
	
	local Data
	
	local success, err = pcall(function()
		Data = StatsDatastore:GetAsync(plr.UserId.."-coins")
	end)
	
	if success then
		if Data then
			Coins.Value = Data
		else
			print("No data found for player", plr.Name)
		end
	else 
		warn(err) 
	end
end

PlayerMod.AddCoins = function(plr, stat, v)--stat is for the future
	if plr and v then
		
		local Coins = plr.PlayerData.Coins
		if Coins then
			Coins.Value += tonumber(v)
		end
	end
end

PlayerMod.SubtractCoins = function(plr, stat, v)
	if plr and v then
		local Coins = plr.PlayerData.Coins
		
		Coins.Value -= tonumber(v)
	end
end

PlayerMod.PlayerRemoving = function(plr)
	if plr then
		local Coins = plr.PlayerData.Coins

		local success, err = pcall(function()
			StatsDatastore:SetAsync(plr.UserId.."-coins", Coins.Value)
		end)
			
		if success then
			print("Sucessfully saved player's data!!", plr.Name)
		else
			warn(err)
		end
	end
end

return PlayerMod

Here’s the code to the UI in case.

GUI

image

EnterKey

local RS = game:GetService("ReplicatedStorage")
local UIS = game:GetService("UserInputService")
local InsertKeyEvent = RS:FindFirstChild("InsertKey")
local GenerateKeyEvent = RS:FindFirstChild("GenerateKey")



local Frame = script.Parent
local Textbox = Frame.TextBox
local EnterButton = Frame.EnterButton
local WarningLabel = Frame.WarningLabel
local SuccessLabel = Frame.SuccessLabel

local EnterKey = Enum.KeyCode.KeypadEnter

UIS.InputBegan:Connect(function(input, gp) --when the player stops interacting with it
	if UIS:GetFocusedTextBox() == Textbox then
		if not gp then
			if input.KeyCode == EnterKey then
				local TargetKey = tostring(Textbox.Text)
				
				InsertKeyEvent:FireServer(TargetKey)
			end
		end
	end
end)

EnterButton.Activated:Connect(function()
	local TargetKey = tostring(Textbox.Text)
	
	InsertKeyEvent:FireServer(TargetKey)
end)

GenerateKeyEvent.OnClientEvent:Connect(function(key)
	if key == nil then
		task.spawn(function()
			WarningLabel.Visible = true
			task.wait(5)
			WarningLabel.Visible = false
		end)
	end
end)

Game

To create a key, simply click “Create Key”
To create a key that provides rewards, simply click “Create Reward” and provide the key to another player
To Remove yourself from the datastore and return back to the menu simply click “Remove Access” and rejoin

1 Like