From 5dbeb6e303777ab144e1da1dd9f16ef17c72edc7 Mon Sep 17 00:00:00 2001 From: Kayne Ruse Date: Tue, 4 Jun 2019 18:47:06 +1000 Subject: [PATCH] This isn't working, let's try another tactic --- server/combat.js | 3 +- server/equipment_statistics.json | 20 +-- server/spying.js | 264 ++++++++++++++++++++++++++++++- src/components/pages/profile.jsx | 1 - 4 files changed, 270 insertions(+), 18 deletions(-) diff --git a/server/combat.js b/server/combat.js index 2599c07..fa115ba 100644 --- a/server/combat.js +++ b/server/combat.js @@ -94,8 +94,7 @@ const attackStatusRequest = (connection) => (req, res) => { res.status(200).json({ status: attacking ? 'attacking' : 'idle', - defender: defender, - msg: null + defender: defender }); res.end(); diff --git a/server/equipment_statistics.json b/server/equipment_statistics.json index e195d9e..bf7e637 100644 --- a/server/equipment_statistics.json +++ b/server/equipment_statistics.json @@ -1,18 +1,18 @@ { "Weapon": { - "Stick": { "cost": 50, "purchasable": true, "saleable": true, "scientistsRequired": 1, "visible": true, "combatBoost": 0.04 }, - "Dagger": { "cost": 75, "purchasable": true, "saleable": true, "scientistsRequired": 2, "visible": true, "combatBoost": 0.06 }, - "Sword": { "cost": 100, "purchasable": true, "saleable": true, "scientistsRequired": 3, "visible": true, "combatBoost": 0.08 }, - "Longsword": { "cost": 150, "purchasable": true, "saleable": true, "scientistsRequired": 4, "visible": true, "combatBoost": 0.10 }, - "Frying Pan": { "cost": 200, "purchasable": true, "saleable": true, "scientistsRequired": 5, "visible": true, "combatBoost": 0.12 } + "Stick": { "cost": 50, "purchasable": true, "saleable": true, "scientistsRequired": 1, "visible": true, "stealable": true, "combatBoost": 0.04 }, + "Dagger": { "cost": 75, "purchasable": true, "saleable": true, "scientistsRequired": 2, "visible": true, "stealable": true, "combatBoost": 0.06 }, + "Sword": { "cost": 100, "purchasable": true, "saleable": true, "scientistsRequired": 3, "visible": true, "stealable": true, "combatBoost": 0.08 }, + "Longsword": { "cost": 150, "purchasable": true, "saleable": true, "scientistsRequired": 4, "visible": true, "stealable": true, "combatBoost": 0.10 }, + "Frying Pan": { "cost": 200, "purchasable": true, "saleable": true, "scientistsRequired": 5, "visible": true, "stealable": true, "combatBoost": 0.12 } }, "Armour": { - "Leather": { "cost": 75, "purchasable": true, "saleable": true, "scientistsRequired": 2, "visible": true, "combatBoost": 0.04 }, - "Gambeson": { "cost": 100, "purchasable": true, "saleable": true, "scientistsRequired": 3, "visible": true, "combatBoost": 0.06 }, - "Chainmail": { "cost": 150, "purchasable": true, "saleable": true, "scientistsRequired": 4, "visible": true, "combatBoost": 0.08 }, - "Platemail": { "cost": 200, "purchasable": true, "saleable": true, "scientistsRequired": 5, "visible": true, "combatBoost": 0.10 } + "Leather": { "cost": 75, "purchasable": true, "saleable": true, "scientistsRequired": 2, "visible": true, "stealable": true, "combatBoost": 0.04 }, + "Gambeson": { "cost": 100, "purchasable": true, "saleable": true, "scientistsRequired": 3, "visible": true, "stealable": true, "combatBoost": 0.06 }, + "Chainmail": { "cost": 150, "purchasable": true, "saleable": true, "scientistsRequired": 4, "visible": true, "stealable": true, "combatBoost": 0.08 }, + "Platemail": { "cost": 200, "purchasable": true, "saleable": true, "scientistsRequired": 5, "visible": true, "stealable": true, "combatBoost": 0.10 } }, "Consumable": { - "Potion": { "cost": 100, "purchasable": true, "saleable": true, "scientistsRequired": 1, "visible": true, "combatBoost": 0.45 } + "Potion": { "cost": 100, "purchasable": true, "saleable": true, "scientistsRequired": 1, "visible": true, "stealable": true, "combatBoost": 0.45 } } } \ No newline at end of file diff --git a/server/spying.js b/server/spying.js index 284dfc5..da87ec3 100644 --- a/server/spying.js +++ b/server/spying.js @@ -8,7 +8,7 @@ let CronJob = require('cron').CronJob; let { logDiagnostics } = require('./diagnostics.js'); let { log } = require('../common/utilities.js'); -let { isSpying } = require('./utilities.js'); +let { getStatistics, isSpying, isAttacking } = require('./utilities.js'); //TODO: rename getStatistics to getEquipmentStatistics const spyRequest = (connection) => (req, res) => { //verify the attacker's credentials (only the attacker can launch an attack) @@ -94,9 +94,7 @@ const spyStatusRequest = (connection) => (req, res) => { res.status(200).json({ status: spying ? 'spying' : 'idle', - attacker: req.body.attacker, - defender: defender, - msg: null + defender: defender }); res.end(); @@ -111,7 +109,263 @@ const spyLogRequest = (connection) => (req, res) => { }; const runSpyTick = (connection) => { - //TODO + //find each pending spy event + let spyTick = new CronJob('* * * * * *', () => { + let query = 'SELECT * FROM pendingSpying WHERE eventTime < CURRENT_TIMESTAMP();'; + connection.query(query, (err, results) => { + if (err) throw err; + + results.forEach((pendingSpying) => { + //check that the attacker still has enough spies + let query = 'SELECT spies FROM profiles WHERE accountId = ?;'; + connection.query(query, [pendingSpying.attackerId], (err, results) => { + if (err) throw err; + + if (results[0].spies < pendingSpying.attackingUnits) { + //delete the failed spying + let query = 'DELETE FROM pendingSpying WHERE id = ?;'; + connection.query(query, [pendingSpying.id], (err) => { + if (err) throw err; + log('Not enough spies for spying', pendingSpying.attackerId, results[0].spies, pendingSpying.attackingUnits); + }); + return; + } + + //spy gameplay logic + spyGameplayLogic(connection, pendingSpying); + }); + }); + }); + }); + + spyTick.start(); +}; + +const spyGameplayLogic = (connection, pendingSpying) => { + //determine how many pairs of defender eyes are available to spot the spies + isAttacking(connection, pendingSpying.defenderId, (err, defenderIsAttacking) => { + if (err) throw err; + isSpying(connection, pendingSpying.defenderId, (err, defenderIsSpying) => { + if (err) throw err; + + let query = 'SELECT * FROM profiles WHERE accountId = ?;'; + connection.query(query, [pendingSpying.defenderId], (err, results) => { + if (err) throw err; + + let totalEyes = results[0].recruits + results[0].soldiers * !defenderIsAttacking + results[0].spies * !defenderIsSpying + results[0].scientists; + + //more spies reduces the chances of being seen? Counter intuitive + let chanceSeen = totalEyes / pendingSpying.attackingUnits; + + //if seen + if (Math.random() * 100 <= chanceSeen) { + let query = 'INSERT INTO pastSpying (eventTime, attackerId, defenderId, attackingUnits, success, spoilsGold) VALUES (?, ?, ?, ?, "failure", 0);'; + connection.query(query, [pendingSpying.eventTime, pendingSpying.attackerId, pendingSpying.defenderId, pendingSpying.attackingUnits], (err) => { + if (err) throw err; + + //spies die on failure + let query = 'UPDATE profiles SET spies = spies - ? WHERE accountId = ?;'; + connection.query(query, [pendingSpying.attackingUnits, pendingSpying.attackerId], (err) => { + if (err) throw err; + + //delete from pending + let query = 'DELETE FROM pendingSpying WHERE id = ?;' + connection.query(query, [pendingSpying.id], (err) => { + if (err) throw err; + + log('Spy failed', pendingSpying.attackerId, pendingSpying.defenderId, pendingSpying.attackingUnits, totalEyes); + }); + }); + }); + } else { + //steal this much gold on success + let spoilsGold = Math.random() >= 0.5 ? Math.floor(results[0].gold * 0.1) : 0; //50% chance of stealing gold + let query = 'INSERT INTO pastSpying (eventTime, attackerId, defenderId, attackingUnits, success, spoilsGold) VALUES (?, ?, ?, ?, "success", ?);'; + connection.query(query, [pendingSpying.eventTime, pendingSpying.attackerId, pendingSpying.defenderId, pendingSpying.attackingUnits, spoilsGold], (err) => { + if (err) throw err; + + let query = 'UPDATE profiles SET gold = gold + ? WHERE accountId = ?;'; + connection.query(query, [spoilsGold, pendingSpying.attackerId], (err) => { + if (err) throw err; + + let query = 'UPDATE profiles SET gold = gold - ? WHERE accountId = ?;'; + connection.query(query, [spoilsGold, pendingSpying.defenderId], (err) => { + if (err) throw err; + + //delete from pending + let query = 'DELETE FROM pendingSpying WHERE id = ?;' + connection.query(query, [pendingSpying.id], (err) => { + if (err) throw err; + + log('Spy succeeded', pendingSpying.attackerId, pendingSpying.defenderId, pendingSpying.attackingUnits, totalEyes, spoilsGold); + + spyStealEquipment(connection, pendingSpying, spoilsGold); + }); + }); + }); + }); + } + }); + });; + }); +}; + +const spyStealEquipment = (connection, pendingSpying, spoilsGold) => { + let query = 'SELECT id FROM pastSpying WHERE eventTime = ? AND attackerId = ? AND defenderId = ? AND spoilsGold = ?;'; //make it VERY hard to grab the wrong one + connection.query(query, [pendingSpying.eventTime, pendingSpying.attackerId, pendingSpying.defenderId, spoilsGold], (err, results) => { + if (err) throw err; + + for (let i = 0; i < pendingSpying.attackingUnits; i++) { + //50% chance of stealing equipment + if (Math.random() >= 0.5 || true) { //DEBUG +log('Attempting to steal'); + spyStealEquipmentInner(connection, pendingSpying.attackerId, pendingSpying.defenderId, results[0].id); + } else { +log('Skipping steal'); + } + } + }); +}; + +const spyStealEquipmentInner = (connection, attackerId, defenderId, pastSpyingId) => { + //NOTE: steal equipment that isn't being carried by soldiers + isAttacking(connection, defenderId, (err, isAttacking) => { + let query = 'SELECT * FROM equipment WHERE accountId = ?;'; + connection.query(query, [defenderId], (err, results) => { //NOTE: async from here on out + if (err) throw err; + + //if he's not attacking, skip to the next step + if (!isAttacking) { + return spyStealEquipmentInnerInner(connection, attackerId, defenderId, results, pastSpyingId); + } + + //count the number of weapons/consumable items to be skipped, from strongest to weakest + let query = 'SELECT soldiers FROM profiles WHERE accountId = ?;'; + connection.query(query, [defenderId], (err, results) => { + if (err) throw err; + + let soldierCount = results[0].soldiers; + + //armour + let query = 'SELECT * FROM equipment WHERE accountId = ? AND type = "Armour";'; + connection.query(query, [defenderId], (err, armourResults) => { + if (err) throw err; + + //NOTE: Armour stays at home - it's never carried by soldiers + + //weapons + let query = 'SELECT * FROM equipment WHERE accountId = ? AND type = "Weapon";'; + connection.query(query, [defenderId], (err, results) => { + if (err) throw err; + + removeForEachSoldier(results, soldierCount, (err, weaponResults) => { + if (err) throw err; + + //consumables + let query = 'SELECT * FROM equipment WHERE accountId = ? AND type = "Consumable";'; + connection.query(query, [defenderId], (err, results) => { + if (err) throw err; + + removeForEachSoldier(results, soldierCount, (err, consumableResults) => { + if (err) throw err; + + //splice the two arrays back together + let results = weaponResults.concat(consumableResults, armourResults); + + spyStealEquipmentInnerInner(connection, attackerId, defenderId, results, pastSpyingId); + }); + }); + }); + }); + }); + }); + }); + }); +}; + +const removeForEachSoldier = (results, soldiers, cb) => { + getStatistics((err, { statistics }) => { + if (err) throw err; + results.sort((a, b) => statistics[a.type][a.name].combatBoost < statistics[b.type][b.name].combatBoost); + + results = results.map((item) => { + //count downwards + if (item.quantity > soldiers) { + item.quantity -= soldiers; + soldiers = 0; + } else { + soldiers -= item.quantity; + item.quantity = 0; + } + + return item; + }); + + results = results.filter(item => item.quantity > 0 && statistics[item.type][item.name].stealable); + + cb(undefined, results); + }); +} + +const spyStealEquipmentInnerInner = (connection, attackerId, defenderId, results, pastSpyingId) => { + //count the total items + let totalItems = 0; + results.forEach((item) => totalItems += item.quantity); + + //select the specific item to steal + let selection = Math.floor(Math.random() * totalItems); + + //find the exact item that will be stolen + let item = results.filter((item) => { + selection -= item.quantity; + if (selection < 0) { + return item; + } + })[0]; + + //NOTE: this is glacially slow + + //insert a new record - will clean up duplicates later + let query = 'INSERT INTO equipment (accountId, name, type, quantity) VALUES (?, ?, ?, 1);'; + connection.query(query, [attackerId, item.name, item.type], (err) => { + if (err) throw err; + + spyStealEquipmentInnerInnerInner(connection, attackerId, defenderId, item, pastSpyingId); + }); +}; + +const spyStealEquipmentInnerInnerInner = (connection, attackerId, defenderId, item, pastSpyingId) => { + //decrement or remove the item + if (item.quantity > 1) { + let query = 'UPDATE equipment SET quantity = quantity - 1 WHERE id = ? AND quantity > 0;'; + connection.query(query, [item.id], (err) => { + if (err) throw err; + + //move on to the next step + // spyStealEquipmentInnerInnerInnerInner(connection, attackerId, defenderId, item, pastSpyingId); +log('MARK 1'); + }); + } else { + let query = 'DELETE FROM equipment WHERE id = ?;'; + connection.query(query, [item.id], (err) => { + if (err) throw err; + + // + // spyStealEquipmentInnerInnerInnerInner(connection, attackerId, defenderId, item, pastSpyingId); +log('MARK 2'); + }) + } +}; + +const spyStealEquipmentInnerInnerInnerInner = (connection, attackerId, defenderId, item, pastSpyingId) => { + //insert into items stolen + let query = 'INSERT INTO equipmentStolen (pastSpyingId, name, type, quantity) VALUES (?, ?, ?, 1);'; + connection.query(query, [pastSpyingId, item.name, item.type], (err) => { + if (err) throw err; + + //Holy nesting, batman! + log('equipment stolen', attackerId, defenderId, item.id, item.name, item.type, pastSpyingId); + }); }; module.exports = { diff --git a/src/components/pages/profile.jsx b/src/components/pages/profile.jsx index c3d7151..0913ee2 100644 --- a/src/components/pages/profile.jsx +++ b/src/components/pages/profile.jsx @@ -256,7 +256,6 @@ class Profile extends React.Component { pendingStatus={'spying'} pendingMsg={'Your spies are spying on'} parseUnits={(json) => json.spies} - disabled={true} >Send Spies