Files
SANCTUM/SERVER_City/server-city.js
2018-11-05 22:20:56 +11:00

482 lines
15 KiB
JavaScript

// .env Variables
require("dotenv").config({path: "../.env"});
//socket.io setup
let server = require("http").createServer();
let io = require("socket.io")(server);
let ioAuth = require("socketio-auth");
ioAuth(io, {
authenticate: function(socket, data, callback) {
return callback(null, data.SERVER_PASS_KEY === process.env.SERVER_PASS_KEY);
},
postAuthenticate: function(socket, data) {
console.log("Authenticated: " + data.username);
socket.client.username = data.username;
}
});
//mysql
let mysql = require("mysql");
let dbConnection = mysql.createConnection({
host: process.env.DATABASE_HOST,
user: process.env.DATABASE_USER,
password: process.env.DATABASE_PASSWORD
});
dbConnection.connect((err) => {
if (err) throw err;
console.log("Connected to the database");
dbConnection.query("USE sanctum;");
});
//shared code
let calcRandom = require('../Shared/calc_random');
//TODO: isolate these responses to specific bots
io.on("connection", async (socket) => {
console.log("made socket connection");
socket.on("disconnect", async () => {
console.log(socket.client.username + " disconnected");
});
socket.on("serverPing", handleServerPing);
socket.on("updateStamina", handleUpdateStamina);
socket.on("conversion", handleConversion);
socket.on("checkin", handleCheckin);
socket.on("wallet", handleWallet);
socket.on("transfer", handleTransfer);
socket.on("userStats", handleUserStats);
socket.on("addXP", handleAddXP);
socket.on("levelUp", handleLevelUp);
socket.on("reviveAll", handleReviveAll);
socket.on("revive", handleRevive);
socket.on("heal", handleHeal);
socket.on("upgrade", handleUpgrade);
});
//listen
server.listen(process.env.SERVER_PORT);
console.log("listening on port " + process.env.SERVER_PORT);
//respond to a ping with a pong
async function handleServerPing({ data }, fn) {
return fn("SERVER PONG!");
}
//update the playerbase's stamina on command
async function handleUpdateStamina({ userID, data }) {
let query = "UPDATE users SET stamina = stamina + 1 WHERE stamina < maxStamina;";
dbConnection.query(query, (err, result) => {
if (err) throw err;
// console.log("updated stamina for all users");
});
};
//handle initial faction join and faction conversions
async function handleConversion({ data }, fn) {
//data[0] = user ID
//data[1] = factionRole
//possible arguments to fn: ["joined", "alreadyJoined", "conversionLocked", "newUser"]
//find the last time this user converted
let query = "SELECT faction FROM users WHERE userID = ? LIMIT 1;";
return dbConnection.query(query, [data[0]], (err, result) => {
if (err) throw err;
//check if this is a new user
if (result.length === 0) {
let query = "INSERT INTO users (userID, faction, factionChanged) VALUES (?, ?, NOW());";
return dbConnection.query(query, [data[0], data[1]], (err, result) => {
if (err) throw err;
dbLog(data[0], "new user", "joined faction " + data[1]);
return fn("newUser");
});
}
//check if already joined this faction
if (result[0].faction == data[1]) { //faction == factionRole
return fn("alreadyJoined");
}
//check if enough time has passed to join a new faction
let query = "SELECT TIME_TO_SEC(TIMEDIFF(NOW(), factionChanged)) FROM users WHERE userID = ? LIMIT 1;";
return dbConnection.query(query, [data[0]], (err, result) => {
if (err) throw err;
if(result[0]['TIME_TO_SEC(TIMEDIFF(NOW(), factionChanged))'] < 60 * 60 * 24 * 7) { //7 days
return fn("conversionLocked"); //too soon
} else {
//update the database with the join
query = "UPDATE users SET faction = ?, factionChanged = NOW() WHERE userID = ?;";
return dbConnection.query(query, [data[1], data[0]], (err, result) => {
if (err) throw err;
dbLog(data[0], "joined", "joined faction " + data[1]);
return fn("joined");
});
}
})
});
}
//handle checkin, and add 1 XP
async function handleCheckin({ data }, fn) { //TODO: You already checked in with me today, you should check in again after X hours
//handle checkins (grant crystal bonus)
//arguments to fn: ["available", time since last checkin], randomAmount
let randomAmount = calcRandom.Random(4, 9);
let query = "SELECT TIME_TO_SEC(TIMEDIFF(NOW(), lastCheckin)) FROM users WHERE userID = ? LIMIT 1;";
return dbConnection.query(query, [data[0]], (err, result) => {
if (err) throw err;
if (result[0]["TIME_TO_SEC(TIMEDIFF(NOW(), lastCheckin))"] == null || result[0]["TIME_TO_SEC(TIMEDIFF(NOW(), lastCheckin))"] > 60 * 60 * 22) { //22 hours
let query = "UPDATE users SET lastCheckin = NOW(), wallet = wallet + ? WHERE userID = ? LIMIT 1;";
return dbConnection.query(query, [randomAmount, data[0]], (err, result) => {
if (err) throw err;
dbLog(data[0], "checkin", "gained " + randomAmount + " to wallet");
addExperience(data[0], 1); //Add 1 XP on every checkin
return fn("available", randomAmount);
});
} else {
return fn(calculateTimeAgo(result[0]["TIME_TO_SEC(TIMEDIFF(NOW(), lastCheckin))"]));
}
});
}
//handle account requests
async function handleWallet({ data }, fn) {
//data[0] = ID of the person to check
let query = "SELECT wallet FROM users WHERE userID = ? LIMIT 1;";
dbConnection.query(query, [data[0]], (err, result) => {
if (err) throw err;
dbLog(data[0], "wallet query", "result: " + result[0].wallet);
return fn(result[0].wallet);
});
}
//handle transfering wallet balance between accounts
async function handleTransfer({ data }, fn) {
console.log("received a transfer request...");
//data[0] = ID of the source account
//data[1] = ID of the destination account
//data[2] = amount to send
//parameters to fn: ["success", "failure"]
//check there's enough in the sender's wallet
let query = "SELECT wallet - ? FROM users WHERE userID = ? LIMIT 1;";
return dbConnection.query(query, [data[2], data[0]], (err, result) => {
if (err) throw err;
//too little in there
if (result[0]["wallet - ?"] < 0) {
return fn("failure");
}
//check the recipient is real
let query = "SELECT * FROM users WHERE userID = ? LIMIT 1;";
return dbConnection.query(query, [data[1]], (err, result) => {
if (err) throw err;
if (result.length == 0) {
return fn("failure");
}
//subtract from the sender
let query = "UPDATE users SET wallet = wallet - ? WHERE userID = ? LIMIT 1;";
return dbConnection.query(query, [data[2], data[0]], (err, result) => {
if (err) throw err;
//add to the recipient
let query = "UPDATE users SET wallet = wallet + ? WHERE userID= ? LIMIT 1;";
return dbConnection.query(query, [data[2], data[1]], (err, result) => {
if (err) throw err;
//finally
dbLog(data[0], "wallet transfer", data[2] + " to " + data[1]);
return fn("success");
});
});
});
});
}
//handle the user stats
async function handleUserStats({ data }, fn) {
console.log("received a userStats request...");
//data[0] = user ID
//parameters to fn: stat structure
let query = "SELECT level, experience, maxHealth, health, maxStamina, stamina, strength, speed, upgradePoints, wallet FROM users WHERE userID = ? LIMIT 1;";
return dbConnection.query(query, [data[0]], (err, result) => {
if (err) throw err;
let stats = {
level: result[0].level,
experience: result[0].experience,
levelProgress: calculateLevelProgress(result[0].experience),
maxHealth: result[0].maxHealth,
health: result[0].health,
maxStamina: result[0].maxStamina,
stamina: result[0].stamina,
strength: result[0].strength,
speed: result[0].speed,
upgradePoints: result[0].upgradePoints,
wallet: result[0].wallet
};
return fn(stats);
});
}
//DEBUGGING?
async function handleAddXP({ userID, data }) {
console.log("received an addXP request...");
//data[0] = amount
addExperience(userID, data[0]);
}
//handle levelling up
async function handleLevelUp({ data }, fn) {
//NOTE: levelling up is handled manually because of reasons
console.log("received a levelUp request...");
//data[0] = user ID
//parameters to fn: ["none", "levelUp"], level, upgradePoints
//get the current level and total amount of experience
let query = "SELECT level, experience, upgradePoints FROM users WHERE userID = ? LIMIT 1;";
return dbConnection.query(query, [data[0]], (err, result) => {
if (err) throw err;
//calculate the correct level, and compare it with the result
let newLevel = Math.floor(calculateLevel(result[0].experience));
//if no levelling, return
if (newLevel == result[0].level) {
return fn("none", result[0].level, result[0].upgradePoints);
}
//handle the level cap
if (newLevel >= process.env.RANK_3_THRESHOLD) {
//update the level, add lootbox
//TODO: add lootbox item
let query = "UPDATE users SET level = ? WHERE userID = ? LIMIT 1;";
return dbConnection.query(query, [newLevel, data[0]], (err, result) => {
if (err) throw err;
//finally, pass the level and upgrade points to the client
let query = "SELECT level, upgradePoints FROM users WHERE userID = ? LIMIT 1;";
return dbConnection.query(query, [data[0]], (err, result) => {
if (err) throw err;
dbLog(data[0], "level max", "level: " + result[0].level + ", upgrade points: " + result[0].upgradePoints + ", lootboxes: ???");
return fn("levelUp", result[0].level, result[0].upgradePoints);
});
});
}
//update the level and the upgrade points
let query = `UPDATE users SET level = ${newLevel}, upgradePoints = upgradePoints + ${newLevel - result[0].level} WHERE userID='${data[0]}' LIMIT 1;`;
return dbConnection.query(query, (err, result) => {
if (err) throw err;
//finally, pass the level and upgrade points to the client
let query = `SELECT level, upgradePoints FROM users WHERE userID='${data[0]}' LIMIT 1;`;
return dbConnection.query(query, (err, result) => {
if (err) throw err;
dbLog(data[0], "level up", "level: " + result[0].level + ", upgrade points: " + result[0].upgradePoints);
return fn("levelUp", result[0].level, result[0].upgradePoints);
});
});
});
}
//handle the daily revives
async function handleReviveAll({ data }, fn) {
console.log("received a reviveAll request...");
//parameters to fn: none
let query = "UPDATE users SET health = maxHealth;";
return dbConnection.query(query, (err, result) => {
if (err) throw err;
return fn();
});
}
//handle reviving a specific player
async function handleRevive({ data }, fn) {
console.log("received a revive request...");
//data[0] = user ID
//data[1] = cost
//data[2] = amount (potentially percentage)
//WARNING: copy/paste
let query = "SELECT health, maxHealth, wallet FROM users WHERE userID = ? LIMIT 1;";
return dbConnection.query(query, [data[0]], (err, result) => {
if (err) throw err;
//not knocked out
if (result[0].health != 0) {
return fn("healNotKnockedOut");
}
return innerHeal(data, fn, result, "revive");
});
}
//handle healing a specific player
async function handleHeal({ data }, fn) {
console.log("received a heal request...");
//data[0] = user ID
//data[1] = cost
//data[2] = amount (potentially percentage)
let query = "SELECT health, maxHealth, wallet FROM users WHERE userID = ? LIMIT 1;";
return dbConnection.query(query, [data[0]], (err, result) => {
if (err) throw err;
//not knocked out
if (result[0].health == 0) {
return fn("healKnockedOut");
}
return innerHeal(data, fn, result, "heal");
});
}
//avoid copy/paste in the healing functions
function innerHeal(data, fn, result, logType = "unknown") {
//not enough money
if (result[0].wallet < data[1]) {
return fn("healNotEnoughInWallet");
}
if (result[0].health == result[0].maxHealth) {
return fn("healFullHealth");
}
//parse out the amount that needs regening
let regenAmount = data[2];
if (regenAmount[regenAmount.length-1] == "%") {
regenAmount = regenAmount.slice(0, -1);
regenAmount = Math.floor(parseFloat(regenAmount) / 100 * result[0].maxHealth);
} else {
regenAmount = Math.floor(parseFloat(regenAmount));
}
//actually do the regen
let newHealth = Math.min(result[0].health + regenAmount, result[0].maxHealth); //I tried making this an SQL function, didn't work
let query = "UPDATE users SET health = ?, wallet = wallet - ? WHERE userID = ? LIMIT 1;";
return dbConnection.query(query, [newHealth, data[1], data[0]], (err, result) => {
if (err) throw err;
//logging touch
let query = "SELECT health, maxHealth FROM users WHERE userID = ? LIMIT 1;";
dbConnection.query(query, [data[0]], (err, result) => {
dbLog(data[0], "health " + logType, "healed " + regenAmount + " - " + result[0].health + "/" + result[0].maxHealth);
});
return fn("healSuccess");
});
}
async function handleUpgrade({ data }, fn) {
console.log("received an upgrade request...");
//data[0] = user ID
//data[1] = statToUpgrade
//fn parameters: ["upgradeSuccess", "upgradeFailure", "upgradeNotEnoughPoints"], suffix
//check the upgrade points
let query = "SELECT upgradePoints FROM users WHERE userID = ? LIMIT 1;";
return dbConnection.query(query, [data[0]], (err, result) => {
if (err) throw err;
if (result[0].upgradePoints == 0) {
return fn("upgradeNotEnoughPoints");
}
//determine the upgrade query
let query = "UPDATE users SET ";
switch(data[1]) {
case "strength":
query += "strength = strength + 1, ";
break;
case "speed":
query += "speed = speed + 1, ";
break;
case "stamina":
query += "maxStamina = maxStamina + 1, stamina = stamina + 1, ";
break;
case "health":
query += "maxHealth = maxHealth + 10, health = health + 10, ";
break;
}
query += "upgradePoints = upgradePoints - 1 WHERE userID = ? LIMIT 1;";
return dbConnection.query(query, data[0], (err, result) => {
if (err) throw err;
dbLog(data[0], "upgrade", "upgrade " + data[1]);//TODO: better log
return fn("upgradeSuccess", data[1] === "health" ? "10 points" : "1 point");
});
});
}
//utility functions
function calculateLevel(experience) {
const levelBase = 1.2;
return levelBase * Math.sqrt(experience);
}
function calculateLevelProgress(experience) {
let level = calculateLevel(experience);
let base = Math.floor(level);
let decimal = level - base;
return Math.floor(decimal * 100); //percentage
}
function addExperience(userID, amount) {
//Add an amount of XP to a user account
let query = "UPDATE users SET experience = experience + ? WHERE userID = ? LIMIT 1;";
return dbConnection.query(query, [amount, userID], (err, result) => {
if (err) throw err;
dbLog(userID, "xp up", "amount added: " + amount);
});
}
function dbLog(id, type, data) {
let query = "INSERT INTO log (discordID, type, data) VALUES (?, ?, ?)";
return dbConnection.query(query, [id, type, data], (err, result) => {
if (err) throw err;
});
}
function calculateTimeAgo(seconds) {
if (seconds < 60) {
return "just now";
}
if (seconds < 60 * 60) {
return "just this hour";
}
if (seconds < 60 * 60 * 24) {
return "today";
}
return "recently";
}