All Code and user interfaces created by FangTasticBeast (ClumsyGiraffe#8765). I wanted to create a project to help a large amount of games on Roblox expand themselves into a new era. The global match system I created does the following:
- Able to create parties and enter them into a queue
- All servers should join the same queue (enables users to cross server join match)
- System should autofill teams (e.g. for squad game a party of 3 and party of 1 will become a team)
- All users should teleport to same reserve server and server maintain the party data
- Unhackable for all features of the system to ensure users cannot create empty matches nor alter their teams
You can test system here. System can be tested with one user since I set the the Solo queue to 1 team with 1 player, however I highly recommend getting 4 players, place one in a free private server, make 1 party of 2 and all parties join Doubles queue.
System v1. I will update systems if new features are launched or bugs are found.
Setup external server
Note: All code written for this step is for Glitch in the format of Node.js. This step can be done in other Cloud computing systems but all modifications will be your responsibility.
-
Create an account on Glitch.com. This stem is relatively simple since the signup process only requires an email.
-
Click on “New project” and click on “glitch-hello-node”. This will take you to your newly created project.
-
Now we need to install all roblox related packages. Simply click on “package.json” and fully replace the code with the code below. Open “Logs” to view when packages are downloaded.
{
"name": "hello-node",
"version": "0.0.1",
"description": "A simple Node app built on fastify, instantly up and running.",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"fastify": "^3.14.1",
"fastify-formbody": "^5.0.0",
"fastify-static": "^4.0.0",
"handlebars": "^4.7.7",
"point-of-view": "^4.14.0",
"noblox.js": "^4.9.0",
"hello-express": "^0.0.3",
"uuid": "^8.3.2"
},
"engines": {
"node": "12.x"
},
"repository": {
"url": "https://glitch.com/edit/#!/glitch-hello-node"
},
"license": "MIT",
"keywords": [
"node",
"glitch",
"express"
]
}
- After packages are installed, go to “server.js” and fully replace the current code with the code below:
const express = require("express");
const rbx = require("noblox.js");
const app = express();
app.use(express.static("public"));
var Modes = [];
Modes.push("Solo");
Modes.push("Double");
Modes.push("Squad");
var Queues = [];
Queues["Solo"] = [];
Queues["Double"] = [];
Queues["Squad"] = [];
var Teams = [];
Teams["Solo"] = [];
Teams["Double"] = [];
Teams["Squad"] = [];
var UsersPerTeam = [];
UsersPerTeam["Solo"] = 1;
UsersPerTeam["Double"] = 2;
UsersPerTeam["Squad"] = 4;
var TeamsPerMatch = [];
TeamsPerMatch["Solo"] = 1;
TeamsPerMatch["Double"] = 2;
TeamsPerMatch["Squad"] = 2;
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
function Recursive(tab, Added, Mode) {
var num = 0;
tab.forEach((party) => {
num += party.length;
});
var ret = [false];
Queues[Mode].forEach((party) => {
if (!Added.includes(party[0]) & !tab.includes(party) & !ret[0]) {
if (num + party.length == UsersPerTeam[Mode]) {
// perfect team exist and it returns //
tab.push(party);
ret = [true, tab];
}
if (num + party.length < UsersPerTeam[Mode]) {
// checks down further the tree //
var tab2 = [...tab];
tab2.push(party);
var res = Recursive(tab2, Added, Mode);
if (res[0]) {
ret = res;
}
}
}
});
return ret;
}
async function UpdateMode(Mode, ServerID) {
var parties = [];
parties.push(ServerID);
parties.push([]);
// Assign all full teams
Queues[Mode].forEach((party) => {
if (party.length == UsersPerTeam[Mode]) {
parties[1].push([party]);
}
});
if (UsersPerTeam[Mode] > 1) {
const Added = [];
Queues[Mode].forEach((party) => {
if (party.length < UsersPerTeam[Mode]) {
if (Added.includes(party[0]) == false) {
// Run a recursive function to check for possible teams //
const results = Recursive([party], Added, Mode);
if (results[0]) {
parties[1].push(results[1]);
results[1].forEach((party3) => {
Added.push(party3[0]);
});
}
}
}
});
}
if (parties[1]) {
if (parties[1].length >= TeamsPerMatch[Mode]) {
Teams[Mode].push([]);
const len = Teams[Mode].length;
Teams[Mode][len - 1].push(ServerID);
var i = 0;
parties[1].forEach((party) => {
if (i < TeamsPerMatch[Mode]) {
Teams[Mode][len - 1][i + 2] = party;
party.forEach((team) => {
Queues[Mode].splice(Queues[Mode].indexOf(team), 1);
});
}
i += 1;
});
Teams[Mode][len - 1][1] = true;
}
}
}
function Check(PartyID, Mode) {
var res = [false];
Teams[Mode].forEach((match) => {
match.forEach((party) => {
if (match.findIndex((party2) => party2 === party) > 1) {
party.forEach((team) => {
if (team[0] == PartyID) {
res = [true, match[0]];
}
});
}
});
});
if (res[0] == false) {
if (Queues[Mode].findIndex((party) => party[0] === PartyID) < 0) {
res = [true, -1];
}
}
return res;
}
// Add party to Queues[Mode] //
app.get("/AddParty", async (req, res) => {
var Ids = req.param("IDs");
var Mode = req.param("Mode");
var ServerID = req.param("ServerID");
const ID = Ids.split("_");
if (Queues[Mode].findIndex((party) => party[0] === ID[0]) < 0) {
await Queues[Mode].push(ID);
UpdateMode(Mode, ServerID);
var bool = true;
var TeleportID = 0;
while (bool) {
await sleep(1000);
var ret = await Check(ID[0], Mode);
if (ret[0]) {
bool = false;
TeleportID = ret[1];
}
}
res.json(TeleportID);
} else {
res.json(-1);
}
});
// Remove party from Queues[Mode] //
app.get("/RemoveParty", async (req, res) => {
var Ids = req.param("IDs");
var Mode = req.param("Mode");
const ID = Ids.split("_");
if (Queues[Mode].findIndex((party) => party[0] === ID[0]) >= 0) {
Queues[Mode].splice(
Queues[Mode].findIndex((party) => party[0] === ID[0]),
1
);
}
res.json("Done");
});
app.get("/CheckJobId", async (req, res) => {
var JobId = req.param("JobId");
var tab = ["Failed"];
await Modes.forEach((Mode) => {
var index = Teams[Mode].findIndex((index) => index[0] === JobId);
if (index >= 0) {
if (Teams[Mode][index][1]) {
tab = [Teams[Mode][index], Mode];
Teams[Mode][index][1] = false;
// You might ask why wait 2 seconds and the boolean system above. The answer is quite simple, if we delete data before user checks their data every second then user will never join. 2 Seconds is just an error handler //
sleep(2000)
Teams[Mode].splice(
Teams[Mode].findIndex((Team) => Team[0] === JobId),
1
);
}
}
});
res.json(tab);
});
const listener = app.listen(process.env.PORT, () => {
console.log("Your app is listening on port " + listener.address().port);
});
- We have complete setting up the server. The only thing left of server is getting the project name. This can be achieved by clicking on “Share” and viewing the name within the link (the orange bit in the ss below). Note that you can change name but I recommend a random one.
Roblox Setup
Note: At the time of writing this Roblox was experiencing problems with viruses in Models. If this doesn’t change I will create bonus steps on how to recreate the models from scratch however I am hoping Roblox fixes this system. As a general advice its always good to have an anti virus plugin to check for viruses in models.
-
Now you need to setup a game (Lobby) and a bonus place within that game (Match). Insert the models into the correct places and insert the children into the correct locations. e.g. As seen in the image below Events, Parties and ServerData should be placed in game.ReplicatedStorage
-
Open “QueueSystem” inside your Lobby under ServerScriptService.
- On line 4 (local ProjectName = “”) insert your External server Project name (The orange line I referred to the last step in Setup external server)
- On line 5 (local TargetPlaceID = 0) replace 0 with the place id of the match (You can retrieve this by going to Roblox>Create>Places> Click on the default place image > Copy numbers in url)
- Open “QueueSystem” inside your Match under ServerScriptService.
- On line 4 (local ProjectName = “”) insert your External server Project name (Same as previous step)
- On line 5 (local TargetPlaceID = 0) replace 0 with the place id of the match (Same as previous step, a trick could be to say game.PlaceId but since I am not certain how script will be used its safer repeating last step)
- On line 6 (local LobbyID= 0) replace 0 with the place id of the Lobby (LobbyId will be the same as the Game’s Id)
- Publish both places
This system is completely independent and requires no external input, however for most projects customizing system will be essential. Below are a few customs that might be helpful to read:
Redoing Front-End
Since most of the system is focused on the backend, it will be logical to redo the user interfaces and customize the programs to fit your needs better. Below are all client to server events (server already ensures all events are not hackable):
- AcceptInvite(Player you want to accept)
- Invite(Player you want to invite)
- Kick(Player you want to kick, can be the same player if they want to leave party)
- JoinQueue(The mode you want to enter, should only be done by party leader otherwise nothing will happen)
- RemoveQueue(Nothing needs to be passed, however this event can be done by any party memeber)
Pausing teleport system untill match completed
In most combat based games the teleporting system might need to be disabled or modified to ensure users don’t mess up gameplay.
The best way to prevent users from teleporting in match would be to add a bonus boolean on line 26 of QueueSystem in the Match place.
-- line 26 --
if party and mode ~= "" then
Showing users how many teams are filled
This system will take a while to add and would increase the amount of http calls. Potentially costing you alot to maintain on Glitch however if you wanted to add system I recommend the following:
Run system as it is and then also add a system (Roblox) to track if there are any users queueing for mode. Then while the party/parties are queued have a simple repetitive function to check if the amount of teams have changed from 0. The glitch part will be the hardest but the “UpdateMode” function can return the amount of parties. As long as this function is ran async it will not effect your glitch process speed.
I would appreciate credit and/or donations for projects that use my system, however since this project is mainly for portfolio purposes and to help the development community I do not expect anything in return. Selling this system is strictly forbidden, in cases where system are forsale along with other work, credit is is required. I do want to note that FangTasticBeast is the only account I use, anyone claiming to be me who doesn’t have access to this account is a fake.