//environment variables require('dotenv').config(); //libraries let formidable = require('formidable'); let CronJob = require('cron').CronJob; let { isAttacking } = require('./combat.js'); //utilities let { log } = require('../common/utilities.js'); //profile creation & requesting const profileCreateRequest = (connection) => (req, res) => { //formidable handles forms let form = formidable.IncomingForm(); //parse form TODO: form? That was a bad idea form.parse(req, (err, fields) => { if (err) throw err; //separate this section so it can be used elsewhere too return profileCreateRequestInner(connection, req, res, fields); }); } function profileCreateRequestInner(connection, req, res, fields) { let query = 'SELECT accountId FROM profiles WHERE accountId IN (SELECT accounts.id FROM accounts WHERE username = ?);'; connection.query(query, [fields.username], (err, results) => { if (err) throw err; if (results.length === 1) { res.status(400).write(log('That profile already exists', fields.username)); res.end(); return; } //check ID, username and token match let query = 'SELECT accountId FROM sessions WHERE accountId IN (SELECT id FROM accounts WHERE username = ?) AND token = ?;'; connection.query(query, [fields.username, fields.token], (err, results) => { if (err) throw err; if (results.length !== 1 || results[0].accountId != fields.id) { res.status(400).write(log('Invalid profile creation credentials', fields.username, fields.id, fields.token)); res.end(); return; } let query = 'INSERT INTO profiles (accountId) SELECT accounts.id FROM accounts WHERE username = ?;'; connection.query(query, [fields.username], (err) => { if (err) throw err; log('Profile created', fields.username, fields.id, fields.token); return profileRequestInner(connection, req, res, fields); }); }); }); } const profileRequest = (connection) => (req, res) => { //formidable handles forms let form = formidable.IncomingForm(); //parse form form.parse(req, (err, fields) => { if (err) throw err; //separate this section so it can be used elsewhere too return profileRequestInner(connection, req, res, fields); }); }; function profileRequestInner(connection, req, res, fields) { let query = 'SELECT * FROM profiles WHERE accountId IN (SELECT accounts.id FROM accounts WHERE username = ?);'; connection.query(query, [fields.username], (err, results) => { if (err) throw err; if (results.length !== 1) { //pass it off to the profile creation process, IF the user is requesting their own profile let query = 'SELECT id FROM accounts WHERE id = ? AND id IN (SELECT accountId FROM sessions WHERE token = ?);'; connection.query(query, [fields.id, fields.token], (err, results) => { if (err) throw err; if (results.length === 1) { return profileCreateRequestInner(connection, req, res, fields); } else { res.status(400).write(log('Profile not found', fields.username, fields.id, fields.token)); res.end(); } }); } else { //results.length === 1 res.status(200).json({ username: fields.username, gold: results[0].gold, recruits: results[0].recruits, soldiers: results[0].soldiers, spies: results[0].spies, scientists: results[0].scientists }); res.end(); log('Profile sent', fields.username, fields.id, fields.token); } }); } //actual actions to be taken const recruitRequest = (connection) => (req, res) => { //formidable handles forms let form = formidable.IncomingForm(); //parse form form.parse(req, (err, fields) => { if (err) throw err; //verify the credentials let query = 'SELECT accountId FROM sessions WHERE accountId = ? AND token = ?;'; connection.query(query, [fields.id, fields.token], (err, results) => { if (err) throw err; if (results.length !== 1) { res.status(400).write(log('Invalid recruit credentials', fields.username, fields.id, fields.token)); res.end(); return; } //verify enough time has passed since the last successful recruit action let query = 'SELECT TIMESTAMPDIFF(HOUR, (SELECT lastRecruitTime FROM profiles WHERE accountId = ?), CURRENT_TIMESTAMP());'; connection.query(query, [fields.id], (err, results) => { if (err) throw err; if (results.length !== 1) { res.status(400).write(log('Invalid database state', fields.username, fields.id, fields.token)); res.end(); return; } let timespans = results[0][Object.keys(results[0])]; //not enough time has passed if (timespans < 22) { res.status(400).write(log('Not enough time has passed', fields.username, fields.id, fields.token)); res.end(); return; } //update the profile with the new data (gaining 1 recruit) let query = 'UPDATE profiles SET recruits = recruits + 1, lastRecruitTime = CURRENT_TIMESTAMP() WHERE accountId = ?;'; connection.query(query, [fields.id], (err) => { if (err) throw err; //send the new profile data as JSON (NOTE: possible duplication) let query = 'SELECT * FROM profiles WHERE accountId = ?;'; connection.query(query, [fields.id], (err, results) => { if (err) throw err; //check just in case if (results.length !== 1) { res.status(400).write(log('Invalid recruit credentials', fields.username, fields.id, fields.token)); res.end(); return; } //results.length === 1 res.status(200).json({ username: fields.username, //TODO: join here gold: results[0].gold, recruits: results[0].recruits, soldiers: results[0].soldiers, spies: results[0].spies, scientists: results[0].scientists }); res.end(); log('Recruit successful', fields.username, fields.id, fields.token); }); }); }); }); }); } const trainRequest = (connection) => (req, res) => { //formidable handles forms let form = formidable.IncomingForm(); //parse form form.parse(req, (err, fields) => { if (err) throw err; //verify the credentials (NOTE: duplication) let query = 'SELECT accountId FROM sessions WHERE accountId = ? AND token = ?;'; connection.query(query, [fields.id, fields.token], (err, results) => { if (err) throw err; if (results.length !== 1) { res.status(400).write(log('Invalid train credentials', fields.username, fields.id, fields.token)); res.end(); return; } //verify the role argument if (fields.role !== 'soldier' && fields.role !== 'spy' && fields.role !== 'scientist') { res.status(400).write(log('Invalid train parameters', fields.username, fields.role, fields.id, fields.token)); res.end(); return; } //can't train while attacking isAttacking(connection, fields.id, (err, attacking) => { if (err) throw err; if (attacking) { res.status(400).write(log('Can\'t train while attacking', fields.id)); res.end(); return; } //determine the cost of the training TODO: make these global for the client too let cost = 0; switch(fields.role) { case 'soldier': cost = 100; break; case 'spy': cost = 200; break; case 'scientist': cost = 120; break; } //verify that the user has a high enough gold and recruit balance let query = 'SELECT recruits, gold FROM profiles WHERE accountId = ?;'; connection.query(query, [fields.id], (err, results) => { if (err) throw err; if (results[0].recruits <= 0) { res.status(400).write(log('Not enough recruits', fields.username, results[0].recruits, fields.id, fields.token)); res.end(); return; } if (results[0].gold < cost) { res.status(400).write(log('Not enough gold', fields.username, results[0].gold, fields.id, fields.token)); res.end(); return; } //update the profile with new values let query = 'UPDATE profiles SET gold = gold - ?, recruits = recruits - 1, soldiers = soldiers + ?, spies = spies + ?, scientists = scientists + ? WHERE accountId = ? AND gold >= ? AND recruits > 0;'; connection.query(query, [cost, fields.role === 'soldier' ? 1 : 0, fields.role === 'spy' ? 1 : 0, fields.role === 'scientist' ? 1 : 0, fields.id, cost], (err) => { if (err) throw err; //send the new profile data as JSON (NOTE: possible duplication) let query = 'SELECT * FROM profiles WHERE accountId = ?;'; connection.query(query, [fields.id], (err, results) => { if (err) throw err; //check just in case if (results.length !== 1) { res.status(400).write(log('Invalid recruit credentials', fields.username, fields.id, fields.token)); res.end(); return; } //results.length === 1 res.status(200).json({ username: fields.username, //TODO: join here gold: results[0].gold, recruits: results[0].recruits, soldiers: results[0].soldiers, spies: results[0].spies, scientists: results[0].scientists }); res.end(); log('Train executed', fields.username, fields.role, fields.id, fields.token); }); }); }); }); }); }); } const untrainRequest = (connection) => (req, res) => { //formidable handles forms let form = formidable.IncomingForm(); //parse form form.parse(req, (err, fields) => { if (err) throw err; //verify the credentials (NOTE: duplication) let query = 'SELECT accountId FROM sessions WHERE accountId = ? AND token = ?;'; connection.query(query, [fields.id, fields.token], (err, results) => { if (err) throw err; if (results.length !== 1) { res.status(400).write(log('Invalid untrain credentials', fields.username, fields.role, fields.id, fields.token)); res.end(); return; } //verify the role argument if (fields.role !== 'soldier' && fields.role !== 'spy' && fields.role !== 'scientist') { res.status(400).write(log('Invalid untrain parameters', fields.username, fields.role, fields.id, fields.token)); res.end(); return; } //can't untrain while attacking isAttacking(connection, fields.id, (err, attacking) => { if (err) throw err; if (attacking) { res.status(400).write(log('Can\'t untrain while attacking', fields.id)); res.end(); return; } //verify that the user has a high enough balance let query = 'SELECT soldiers, spies, scientists FROM profiles WHERE accountId = ?;'; connection.query(query, [fields.id], (err, results) => { if (err) throw err; if (fields.role === 'soldier' && results[0].soldiers <= 0) { res.status(400).write(log('Not enough soldiers', fields.username, results[0].soldiers, fields.id, fields.token)); res.end(); return; } if (fields.role === 'spy' && results[0].spies <= 0) { res.status(400).write(log('Not enough spies', fields.username, results[0].spies, fields.id, fields.token)); res.end(); return; } if (fields.role === 'scientist' && results[0].scientists <= 0) { res.status(400).write(log('Not enough scientists', fields.username, results[0].scientists, fields.id, fields.token)); res.end(); return; } //hacky let role = null; if (fields.role === 'soldier') { role = 'soldiers'; } else if (fields.role === 'spy') { role = 'spies'; } else if (fields.role === 'scientist') { role = 'scientists'; } else { res.status(400).write(log('Unknown role found', fields.role)); res.end(); return; } //update the profile with new values let query = `UPDATE profiles SET recruits = recruits + 1, soldiers = soldiers - ?, spies = spies - ?, scientists = scientists - ? WHERE accountId = ? AND ${role} > 0;`; connection.query(query, [fields.role === 'soldier' ? 1 : 0, fields.role === 'spy' ? 1 : 0, fields.role === 'scientist' ? 1 : 0, fields.id], (err) => { if (err) throw err; //send the new profile data as JSON (NOTE: possible duplication) let query = 'SELECT * FROM profiles WHERE accountId = ?;'; connection.query(query, [fields.id], (err, results) => { if (err) throw err; //check just in case if (results.length !== 1) { res.status(400).write(log('Invalid untrain credentials', fields.username, fields.role, fields.id, fields.token)); res.end(); return; } //results.length === 1 res.status(200).json({ username: fields.username, //TODO: join here gold: results[0].gold, recruits: results[0].recruits, soldiers: results[0].soldiers, spies: results[0].spies, scientists: results[0].scientists }); res.end(); log('Untrain executed', fields.username, fields.role, fields.id, fields.token); }); }); }); }); }); }); } const ladderRequest = (connection) => (req, res) => { let query = 'SELECT username, soldiers, recruits, gold FROM accounts JOIN profiles ON accounts.id = profiles.accountId ORDER BY soldiers DESC, recruits DESC, gold DESC LIMIT ?, ?;'; connection.query(query, [req.body.start, req.body.length], (err, results) => { if (err) throw err; res.status(200).json(results); log('Ladder sent', req.body.start, req.body.length, results); }); } const runGoldTick = (connection) => { let goldTickJob = new CronJob('0 */30 * * * *', () => { let query = 'UPDATE profiles SET gold = gold + recruits;'; connection.query(query, (err) => { if (err) throw err; log('Gold tick'); }); }); goldTickJob.start(); } module.exports = { // profileCreate: profileCreate, //NOTE: Not actually used profileRequest: profileRequest, recruitRequest: recruitRequest, trainRequest: trainRequest, untrainRequest: untrainRequest, ladderRequest: ladderRequest, runGoldTick: runGoldTick }