From daf7c9b1570e1a00974394ba706f26d565292f92 Mon Sep 17 00:00:00 2001 From: Kayne Ruse Date: Thu, 30 May 2019 06:53:49 +1000 Subject: [PATCH] Locked off training and untrained while attacking --- common/utilities.js | 5 +- public/news/2019-05-30-01.md | 1 + server/combat.js | 124 +++++++++++-------- server/profiles.js | 198 +++++++++++++++++-------------- src/components/pages/profile.jsx | 3 +- 5 files changed, 194 insertions(+), 137 deletions(-) diff --git a/common/utilities.js b/common/utilities.js index b854cf4..f24f840 100644 --- a/common/utilities.js +++ b/common/utilities.js @@ -17,7 +17,10 @@ let excluded = [ //messages that should not be logged 'attacking', 'idle', - 'Combat log sent' + 'Combat log sent', + + 'Can\'t train while attacking', + 'Can\'t untrain while attacking' ]; const log = (msg, ...args) => { diff --git a/public/news/2019-05-30-01.md b/public/news/2019-05-30-01.md index d85a29e..e26d8b0 100644 --- a/public/news/2019-05-30-01.md +++ b/public/news/2019-05-30-01.md @@ -8,6 +8,7 @@ So changes I've made today: * Google analytics implemented. * The game ladder can now point to a specific rank with the URL: [https://kingdombattles.net/ladder?rank=0](ladder?rank=0) * The same goes for the profile's combat log: [https://kingdombattles.net/profile?log=0](profile?log=0) +* You can no longer train and untrain while attacking. Known Bugs: diff --git a/server/combat.js b/server/combat.js index 2e53bc9..e655902 100644 --- a/server/combat.js +++ b/server/combat.js @@ -48,8 +48,10 @@ const attackRequest = (connection) => (req, res) => { let attackingUnits = results[0].soldiers; //verify that the attacker is not already attacking someone - isAttacking(connection, req.body.attacker, (isAttacking) => { - if (isAttacking) { + isAttacking(connection, req.body.attacker, (err, attacking) => { + if (err) throw err; + + if (attacking) { res.status(400).write(log('You are already attacking someone', req.body.attacker, req.body.defender)); res.end(); return; @@ -75,9 +77,11 @@ const attackRequest = (connection) => (req, res) => { } const attackStatusRequest = (connection) => (req, res) => { - isAttacking(connection, req.body.username, (isAttacking, defender) => { + isAttacking(connection, req.body.username, (err, attacking, defender) => { + if (err) throw err; + res.status(200).json({ - status: log(isAttacking ? 'attacking' : 'idle', req.body.username, defender), + status: log(attacking ? 'attacking' : 'idle', req.body.username, defender), defender: defender }); @@ -103,55 +107,73 @@ const runCombatTick = (connection) => { if (err) throw err; results.forEach((pendingCombat) => { - //get the defender's undefended status - isAttacking(connection, pendingCombat.defenderId, (undefended) => { - //get the defending unit count, gold - let query = 'SELECT soldiers, recruits, gold FROM profiles WHERE accountId = ?;'; + //check that the attacker still has enough soliders + let query = 'SELECT soldiers FROM profiles WHERE accountId = ?;'; + connection.query(query, [pendingCombat.attackerId], (err, results) => { + if (err) throw err; - connection.query(query, [pendingCombat.defenderId], (err, results) => { + if (results[0].soldiers < pendingCombat.attackingUnits) { + //delete the failed combat + let query = 'DELETE FROM pendingCombat WHERE id = ?;'; + connection.query(query, [pendingCombat.id], (err) => { + if (err) throw err; + log('Not enough soldiers for attack', pendingCombat.attackerId, results[0].soldiers, pendingCombat.attackingUnits); + }); + return; + } + + //get the defender's undefended status + isAttacking(connection, pendingCombat.defenderId, (err, undefended) => { if (err) throw err; - let defendingUnits; - if (!undefended && results[0].soldiers > 0) { - defendingUnits = results[0].soldiers; - } else { - defendingUnits = results[0].recruits; - } + //get the defending unit count, gold + let query = 'SELECT soldiers, recruits, gold FROM profiles WHERE accountId = ?;'; - //determine the victor - let rand = Math.random() * (pendingCombat.attackingUnits + defendingUnits * (undefended ? 0.25 : 1)); - let victor = rand <= pendingCombat.attackingUnits ? 'attacker' : 'defender'; - - //determine the spoils and casualties - let spoilsGold = Math.floor(results[0].gold * (victor === 'attacker' ? 0.1 : 0.02)); - let casualtiesVictor = Math.floor((pendingCombat.attackingUnits >= 10 ? pendingCombat.attackingUnits - 10 : 0) * (victor === 'attacker' ? 0.05 : 0.1)); - - //NOTE: there is a negative gold bug somewhere - if (spoilsGold <= 0) { - log('WARNING: spoilsGold <= 0', pendingCombat.attackerId, pendingCombat.defenderId, spoilsGold); - } - - //save the combat - let query = 'INSERT INTO pastCombat (eventTime, attackerId, defenderId, attackingUnits, defendingUnits, undefended, victor, spoilsGold, casualtiesVictor) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);'; - connection.query(query, [pendingCombat.eventTime, pendingCombat.attackerId, pendingCombat.defenderId, pendingCombat.attackingUnits, defendingUnits, undefended, victor, spoilsGold, casualtiesVictor], (err) => { + connection.query(query, [pendingCombat.defenderId], (err, results) => { if (err) throw err; - //update the attacker profile - let query = 'UPDATE profiles SET gold = gold + ?, soldiers = soldiers - ? WHERE id = ?;'; - connection.query(query, [spoilsGold, casualtiesVictor, pendingCombat.attackerId], (err) => { + let defendingUnits; + if (!undefended && results[0].soldiers > 0) { + defendingUnits = results[0].soldiers; + } else { + defendingUnits = results[0].recruits; + } + + //determine the victor + let rand = Math.random() * (pendingCombat.attackingUnits + defendingUnits * (undefended ? 0.25 : 1)); + let victor = rand <= pendingCombat.attackingUnits ? 'attacker' : 'defender'; + + //determine the spoils and casualties + let spoilsGold = Math.floor(results[0].gold * (victor === 'attacker' ? 0.1 : 0.02)); + let casualtiesVictor = Math.floor((pendingCombat.attackingUnits >= 10 ? pendingCombat.attackingUnits - 10 : 0) * (victor === 'attacker' ? 0.05 : 0.1)); + + //NOTE: there is a negative gold bug somewhere + if (spoilsGold <= 0) { + log('WARNING: spoilsGold <= 0', pendingCombat.attackerId, pendingCombat.defenderId, spoilsGold); + } + + //save the combat + let query = 'INSERT INTO pastCombat (eventTime, attackerId, defenderId, attackingUnits, defendingUnits, undefended, victor, spoilsGold, casualtiesVictor) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);'; + connection.query(query, [pendingCombat.eventTime, pendingCombat.attackerId, pendingCombat.defenderId, pendingCombat.attackingUnits, defendingUnits, undefended, victor, spoilsGold, casualtiesVictor], (err) => { if (err) throw err; - //update the defender profile - let query = 'UPDATE profiles SET gold = gold - ? WHERE id = ?;'; - connection.query(query, [spoilsGold, pendingCombat.defenderId], (err) => { + //update the attacker profile + let query = 'UPDATE profiles SET gold = gold + ?, soldiers = soldiers - ? WHERE id = ?;'; + connection.query(query, [spoilsGold, casualtiesVictor, pendingCombat.attackerId], (err) => { if (err) throw err; - //delete the pending combat - let query = 'DELETE FROM pendingCombat WHERE id = ?;'; - connection.query(query, [pendingCombat.id], (err) => { + //update the defender profile + let query = 'UPDATE profiles SET gold = gold - ? WHERE id = ?;'; + connection.query(query, [spoilsGold, pendingCombat.defenderId], (err) => { if (err) throw err; - log('Combat executed', pendingCombat.attackerId, pendingCombat.defenderId, victor); + //delete the pending combat + let query = 'DELETE FROM pendingCombat WHERE id = ?;'; + connection.query(query, [pendingCombat.id], (err) => { + if (err) throw err; + + log('Combat executed', pendingCombat.attackerId, pendingCombat.defenderId, victor); + }); }); }); }); @@ -165,26 +187,33 @@ const runCombatTick = (connection) => { combatTick.start(); } +const isNormalInteger = (str) => { + let n = Math.floor(Number(str)); + return n !== Infinity && String(n) == str && n >= 0; +} + const isAttacking = (connection, user, cb) => { let query; - if (typeof(user) === 'string') { - query = 'SELECT * FROM pendingCombat WHERE attackerId IN (SELECT id FROM accounts WHERE username = ?);'; - } else if (typeof(user) === 'number') { + if (isNormalInteger(user)) { query = 'SELECT * FROM pendingCombat WHERE attackerId = ?;'; + } else if (typeof(user) === 'string') { + query = 'SELECT * FROM pendingCombat WHERE attackerId IN (SELECT id FROM accounts WHERE username = ?);'; + } else { + return cb('Unknown argument type for user'); } connection.query(query, [user], (err, results) => { if (err) throw err; if (results.length === 0) { - cb(false); + return cb(undefined, false); } else { //get the username of the person being attacked let query = 'SELECT username FROM accounts WHERE id = ?;'; connection.query(query, [results[0].defenderId], (err, results) => { if (err) throw err; - cb(true, results[0].username); + return cb(undefined, true, results[0].username); }); } }); @@ -194,7 +223,8 @@ module.exports = { attackRequest: attackRequest, attackStatusRequest: attackStatusRequest, combatLogRequest: combatLogRequest, - runCombatTick: runCombatTick + runCombatTick: runCombatTick, + isAttacking: isAttacking } /* diff --git a/server/profiles.js b/server/profiles.js index 8ba6dba..3f2af7b 100644 --- a/server/profiles.js +++ b/server/profiles.js @@ -5,6 +5,8 @@ require('dotenv').config(); let formidable = require('formidable'); let CronJob = require('cron').CronJob; +let { isAttacking } = require('./combat.js'); + //utilities let { log } = require('../common/utilities.js'); @@ -204,67 +206,78 @@ const trainRequest = (connection) => (req, res) => { 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) => { + //can't train while attacking + isAttacking(connection, fields.id, (err, attacking) => { 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)); + if (attacking) { + res.status(400).write(log('Can\'t train while attacking', fields.id)); 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; + //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; } - //update the profile with new values - let query = 'UPDATE profiles SET gold = gold - ?, recruits = recruits - 1, soldiers = soldiers + ?, spies = spies + ?, scientists = scientists + ? WHERE accountId = ?;'; - connection.query(query, [cost, fields.role === 'soldier' ? 1 : 0, fields.role === 'spy' ? 1 : 0, fields.role === 'scientist' ? 1 : 0, fields.id], (err) => { + //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; - //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 (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 = ?;'; + connection.query(query, [cost, fields.role === 'soldier' ? 1 : 0, fields.role === 'spy' ? 1 : 0, fields.role === 'scientist' ? 1 : 0, fields.id], (err) => { 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; - } + //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; - //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 + //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 successful', fields.username, fields.role, fields.id, fields.token); }); - res.end(); - log('Train successful', fields.username, fields.role, fields.id, fields.token); }); }); }); @@ -298,57 +311,68 @@ const untrainRequest = (connection) => (req, res) => { 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) => { + //can't untrain while attacking + isAttacking(connection, fields.id, (err, attacking) => { 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)); + + if (attacking) { + res.status(400).write(log('Can\'t untrain while attacking', fields.id)); 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; - } - - //update the profile with new values - let query = 'UPDATE profiles SET recruits = recruits + 1, soldiers = soldiers - ?, spies = spies - ?, scientists = scientists - ? WHERE accountId = ?;'; - connection.query(query, [fields.role === 'soldier' ? 1 : 0, fields.role === 'spy' ? 1 : 0, fields.role === 'scientist' ? 1 : 0, fields.id], (err) => { + //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; - //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 (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; + } + + //update the profile with new values + let query = 'UPDATE profiles SET recruits = recruits + 1, soldiers = soldiers - ?, spies = spies - ?, scientists = scientists - ? WHERE accountId = ?;'; + 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; - //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; - } + //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; - //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 + //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 successful', fields.username, fields.role, fields.id, fields.token); }); - res.end(); - log('Untrain successful', fields.username, fields.role, fields.id, fields.token); }); }); }); diff --git a/src/components/pages/profile.jsx b/src/components/pages/profile.jsx index a037b2d..7f32273 100644 --- a/src/components/pages/profile.jsx +++ b/src/components/pages/profile.jsx @@ -324,8 +324,7 @@ class Profile extends React.Component { ProfileNotFoundMainPanel() { return ( -
-
+
); }