Finished implementing equipment
This commit is contained in:
+114
-51
@@ -7,6 +7,8 @@ let CronJob = require('cron').CronJob;
|
|||||||
//utilities
|
//utilities
|
||||||
let { log } = require('../common/utilities.js');
|
let { log } = require('../common/utilities.js');
|
||||||
|
|
||||||
|
let { getStatistics, isAttacking } = require('./utilities.js');
|
||||||
|
|
||||||
const attackRequest = (connection) => (req, res) => {
|
const attackRequest = (connection) => (req, res) => {
|
||||||
//verify the attacker's credentials (only the attacker can launch an attack)
|
//verify the attacker's credentials (only the attacker can launch an attack)
|
||||||
let query = 'SELECT COUNT(*) AS total FROM sessions WHERE accountId = ? AND accountId IN (SELECT id FROM accounts WHERE username = ?) AND token = ?;';
|
let query = 'SELECT COUNT(*) AS total FROM sessions WHERE accountId = ? AND accountId IN (SELECT id FROM accounts WHERE username = ?) AND token = ?;';
|
||||||
@@ -140,36 +142,102 @@ const runCombatTick = (connection) => {
|
|||||||
defendingUnits = results[0].recruits;
|
defendingUnits = results[0].recruits;
|
||||||
}
|
}
|
||||||
|
|
||||||
//determine the victor
|
//get the attacker equipment
|
||||||
//TODO: add equipment effectiveness
|
let query = 'SELECT * FROM equipment WHERE accountId = ? AND type = "Weapon";';
|
||||||
let rand = Math.random() * (pendingCombat.attackingUnits + defendingUnits * (undefended ? 0.25 : 1));
|
connection.query(query, [pendingCombat.attackerId], (err, attackerEquipment) => {
|
||||||
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 attackerCasualties = Math.floor((pendingCombat.attackingUnits >= 10 ? pendingCombat.attackingUnits - 10 : 0) * (victor === 'attacker' ? 0.05 : 0.1));
|
|
||||||
|
|
||||||
//save the combat
|
|
||||||
let query = 'INSERT INTO pastCombat (eventTime, attackerId, defenderId, attackingUnits, defendingUnits, undefended, victor, spoilsGold, attackerCasualties) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);';
|
|
||||||
connection.query(query, [pendingCombat.eventTime, pendingCombat.attackerId, pendingCombat.defenderId, pendingCombat.attackingUnits, defendingUnits, undefended, victor, spoilsGold, attackerCasualties], (err) => {
|
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
|
|
||||||
//update the attacker profile
|
//get the defender equipment
|
||||||
let query = 'UPDATE profiles SET gold = gold + ?, soldiers = soldiers - ? WHERE accountId = ?;';
|
let query = 'SELECT * FROM equipment WHERE accountId = ? AND type = "Armour";';
|
||||||
connection.query(query, [spoilsGold, attackerCasualties, pendingCombat.attackerId], (err) => {
|
connection.query(query, [pendingCombat.defenderId], (err, defenderEquipment) => {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
|
|
||||||
//update the defender profile
|
//get the attacker consumables
|
||||||
let query = 'UPDATE profiles SET gold = gold - ? WHERE accountId = ?;';
|
let query = 'SELECT * FROM equipment WHERE accountId = ? AND type = "Consumable";';
|
||||||
connection.query(query, [spoilsGold, pendingCombat.defenderId], (err) => {
|
connection.query(query, [pendingCombat.attackerId], (err, attackerConsumables) => {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
|
|
||||||
//delete the pending combat
|
//get the defender consumables
|
||||||
let query = 'DELETE FROM pendingCombat WHERE id = ?;';
|
let query = 'SELECT * FROM equipment WHERE accountId = ? AND type = "Consumable";';
|
||||||
connection.query(query, [pendingCombat.id], (err) => {
|
connection.query(query, [pendingCombat.defenderId], (err, defenderConsumables) => {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
|
|
||||||
log('Combat executed', pendingCombat.attackerId, pendingCombat.defenderId, victor, spoilsGold);
|
//get the global equipment stats
|
||||||
|
getStatistics((err, { statistics }) => {
|
||||||
|
if (err) throw err;
|
||||||
|
|
||||||
|
//get the combat boosts from equipment, from highest to lowest
|
||||||
|
attackerEquipment.sort((a, b) => statistics[a.type][a.name].combatBoost < statistics[b.type][b.name].combatBoost);
|
||||||
|
let attackerEquipmentBoost = 0;
|
||||||
|
for (let i = 0; i < pendingCombat.attackingUnits; i++) {
|
||||||
|
attackerEquipmentBoost += attackerEquipment[i] ? statistics[attackerEquipment[i].type][attackerEquipment[i].name].combatBoost : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
defenderEquipment.sort((a, b) => statistics[a.type][a.name].combatBoost < statistics[b.type][b.name].combatBoost);
|
||||||
|
let defenderEquipmentBoost = 0;
|
||||||
|
for (let i = 0; i < defendingUnits; i++) {
|
||||||
|
defenderEquipmentBoost += defenderEquipment[i] ? statistics[defenderEquipment[i].type][defenderEquipment[i].name].combatBoost : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//get the boosts from consumables
|
||||||
|
attackerConsumables.sort((a, b) => statistics[a.type][a.name].combatBoost < statistics[b.type][b.name].combatBoost);
|
||||||
|
let attackerConsumablesBoost = 0;
|
||||||
|
for (let i = 0; i < pendingCombat.attackingUnits; i++) {
|
||||||
|
attackerConsumablesBoost += attackerConsumables[i] ? statistics[attackerConsumables[i].type][attackerConsumables[i].name].combatBoost : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
defenderConsumables.sort((a, b) => { statistics[a.type][a.name].combatBoost < statistics[b.type][b.name].combatBoost});
|
||||||
|
let defenderConsumablesBoost = 0;
|
||||||
|
for (let i = 0; i < defendingUnits; i++) {
|
||||||
|
defenderConsumablesBoost += defenderConsumables[i] ? statistics[defenderConsumables[i].type][defenderConsumables[i].name].combatBoost : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//determine the victor (defender wants high rand, attacker wants low rand)
|
||||||
|
let rand = Math.random() * (pendingCombat.attackingUnits + defenderEquipmentBoost + defenderConsumablesBoost + defendingUnits * (undefended ? 0.25 : 1));
|
||||||
|
let victor = rand <= attackerEquipmentBoost + attackerConsumablesBoost + pendingCombat.attackingUnits ? 'attacker' : 'defender';
|
||||||
|
|
||||||
|
//determine the spoils and casualties
|
||||||
|
let spoilsGold = Math.floor(results[0].gold * (victor === 'attacker' ? 0.1 : 0.02));
|
||||||
|
let attackerCasualties = Math.floor((pendingCombat.attackingUnits >= 10 ? pendingCombat.attackingUnits : 0) * (victor === 'attacker' ? Math.random() / 5 : Math.random() / 2));
|
||||||
|
|
||||||
|
//save the combat
|
||||||
|
let query = 'INSERT INTO pastCombat (eventTime, attackerId, defenderId, attackingUnits, defendingUnits, undefended, victor, spoilsGold, attackerCasualties) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);';
|
||||||
|
connection.query(query, [pendingCombat.eventTime, pendingCombat.attackerId, pendingCombat.defenderId, pendingCombat.attackingUnits, defendingUnits, undefended, victor, spoilsGold, attackerCasualties], (err) => {
|
||||||
|
if (err) throw err;
|
||||||
|
|
||||||
|
//update the attacker profile
|
||||||
|
let query = 'UPDATE profiles SET gold = gold + ?, soldiers = soldiers - ? WHERE accountId = ?;';
|
||||||
|
connection.query(query, [spoilsGold, attackerCasualties, pendingCombat.attackerId], (err) => {
|
||||||
|
if (err) throw err;
|
||||||
|
|
||||||
|
//update the defender profile
|
||||||
|
let query = 'UPDATE profiles SET gold = gold - ? WHERE accountId = ?;';
|
||||||
|
connection.query(query, [spoilsGold, pendingCombat.defenderId], (err) => {
|
||||||
|
if (err) throw err;
|
||||||
|
|
||||||
|
//remove used consumables (moved because callback hell is rediculous)
|
||||||
|
removeConsumables(connection, attackerConsumables, pendingCombat.attackingUnits);
|
||||||
|
removeConsumables(connection, defenderConsumables, defendingUnits);
|
||||||
|
|
||||||
|
//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, spoilsGold);
|
||||||
|
|
||||||
|
//clean the database
|
||||||
|
let query = 'DELETE FROM equipment WHERE quantity <= 0;';
|
||||||
|
connection.query(query, (err) => {
|
||||||
|
if (err) throw err;
|
||||||
|
|
||||||
|
log('Cleaned database', 'Combat consumables');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -184,42 +252,37 @@ const runCombatTick = (connection) => {
|
|||||||
combatTick.start();
|
combatTick.start();
|
||||||
};
|
};
|
||||||
|
|
||||||
const isNormalInteger = (str) => {
|
//Part of runCombatTick
|
||||||
let n = Math.floor(Number(str));
|
let removeConsumables = (connection, consumables, number) => {
|
||||||
return n !== Infinity && String(n) == str && n >= 0;
|
if (number > 0 && consumables.length > 0) {
|
||||||
};
|
//if not rolling to the next stack after this
|
||||||
|
if (number - consumables[0].quantity <= 0) {
|
||||||
const isAttacking = (connection, user, cb) => {
|
let query = 'UPDATE equipment SET quantity = quantity - ? WHERE id = ?;';
|
||||||
let query;
|
connection.query(query, [number, consumables[0].id], (err) => {
|
||||||
|
|
||||||
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: ${typeof(user)}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.query(query, [user], (err, results) => {
|
|
||||||
if (err) throw err;
|
|
||||||
|
|
||||||
if (results.length === 0) {
|
|
||||||
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;
|
if (err) throw err;
|
||||||
return cb(undefined, true, results[0].username);
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
} else { //will be rolling to the next stack after this
|
||||||
|
let query = 'UPDATE equipment SET quantity = 0 WHERE id = ?;';
|
||||||
|
|
||||||
|
connection.query(query, [consumables[0].id], (err) => {
|
||||||
|
if (err) throw err;
|
||||||
|
|
||||||
|
//tick
|
||||||
|
number -= consumables[0].quantity;
|
||||||
|
consumables.shift();
|
||||||
|
|
||||||
|
//it took me two hours to write this line; you can't make functions inside loops
|
||||||
|
return removeConsumables(connection, consumables, number);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
attackRequest: attackRequest,
|
attackRequest: attackRequest,
|
||||||
attackStatusRequest: attackStatusRequest,
|
attackStatusRequest: attackStatusRequest,
|
||||||
combatLogRequest: combatLogRequest,
|
combatLogRequest: combatLogRequest,
|
||||||
runCombatTick: runCombatTick,
|
runCombatTick: runCombatTick
|
||||||
isAttacking: isAttacking
|
|
||||||
};
|
};
|
||||||
|
|||||||
+1
-24
@@ -4,30 +4,7 @@ require('dotenv').config();
|
|||||||
//utilities
|
//utilities
|
||||||
let { log } = require('../common/utilities.js');
|
let { log } = require('../common/utilities.js');
|
||||||
|
|
||||||
let { isAttacking } = require('./combat.js');
|
let { getStatistics, getOwned, isAttacking } = require('./utilities.js');
|
||||||
|
|
||||||
const getStatistics = (cb) => {
|
|
||||||
//TODO: apiVisible field
|
|
||||||
return cb(undefined, { 'statistics': require('./equipment_statistics.json') });
|
|
||||||
};
|
|
||||||
|
|
||||||
const getOwned = (connection, id, cb) => {
|
|
||||||
let query = 'SELECT name, quantity FROM equipment WHERE accountId = ?;';
|
|
||||||
connection.query(query, [id], (err, results) => {
|
|
||||||
if (err) throw err;
|
|
||||||
|
|
||||||
let ret = {};
|
|
||||||
|
|
||||||
Object.keys(results).map((key) => {
|
|
||||||
if (ret[results[key].name] !== undefined) {
|
|
||||||
log('WARNING: Invalid database state, equipment owned', id, JSON.stringify(results));
|
|
||||||
}
|
|
||||||
ret[results[key].name] = results[key].quantity;
|
|
||||||
});
|
|
||||||
|
|
||||||
return cb(undefined, { 'owned': ret });
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const equipmentRequest = (connection) => (req, res) => {
|
const equipmentRequest = (connection) => (req, res) => {
|
||||||
//validate the credentials
|
//validate the credentials
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
{
|
{
|
||||||
"Weapons": {
|
"Weapon": {
|
||||||
"Stick": { "cost": 50, "purchasable": true, "saleable": true, "scientistsRequired": 1, "visible": true, "combatBoost": 0.02 },
|
"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.03 },
|
"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.04 },
|
"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.05 },
|
"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.06 }
|
"Frying Pan": { "cost": 200, "purchasable": true, "saleable": true, "scientistsRequired": 5, "visible": true, "combatBoost": 0.12 }
|
||||||
},
|
},
|
||||||
"Armour": {
|
"Armour": {
|
||||||
"Leather": { "cost": 75, "purchasable": false, "saleable": true, "scientistsRequired": 2, "visible": true, "combatBoost": 0.02 },
|
"Leather": { "cost": 75, "purchasable": true, "saleable": true, "scientistsRequired": 2, "visible": true, "combatBoost": 0.04 },
|
||||||
"Gambeson": { "cost": 100, "purchasable": false, "saleable": true, "scientistsRequired": 3, "visible": true, "combatBoost": 0.03 },
|
"Gambeson": { "cost": 100, "purchasable": true, "saleable": true, "scientistsRequired": 3, "visible": true, "combatBoost": 0.06 },
|
||||||
"Chainmail": { "cost": 150, "purchasable": false, "saleable": true, "scientistsRequired": 4, "visible": true, "combatBoost": 0.04 },
|
"Chainmail": { "cost": 150, "purchasable": true, "saleable": true, "scientistsRequired": 4, "visible": true, "combatBoost": 0.08 },
|
||||||
"Platemail": { "cost": 200, "purchasable": false, "saleable": true, "scientistsRequired": 5, "visible": true, "combatBoost": 0.05 }
|
"Platemail": { "cost": 200, "purchasable": true, "saleable": true, "scientistsRequired": 5, "visible": true, "combatBoost": 0.10 }
|
||||||
},
|
},
|
||||||
"Consumables": {
|
"Consumable": {
|
||||||
"Potion": { "cost": 100, "purchasable": true, "saleable": true, "scientistsRequired": 1, "visible": true, "combatBoost": 0.04 }
|
"Potion": { "cost": 100, "purchasable": true, "saleable": true, "scientistsRequired": 1, "visible": true, "combatBoost": 0.45 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -4,7 +4,7 @@ require('dotenv').config();
|
|||||||
//libraries
|
//libraries
|
||||||
let CronJob = require('cron').CronJob;
|
let CronJob = require('cron').CronJob;
|
||||||
|
|
||||||
let { isAttacking } = require('./combat.js');
|
let { isAttacking } = require('./utilities.js');
|
||||||
|
|
||||||
//utilities
|
//utilities
|
||||||
let { log } = require('../common/utilities.js');
|
let { log } = require('../common/utilities.js');
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
//environment variables
|
||||||
|
require('dotenv').config();
|
||||||
|
|
||||||
|
//utilities
|
||||||
|
let { log } = require('../common/utilities.js');
|
||||||
|
|
||||||
|
const getStatistics = (cb) => {
|
||||||
|
//TODO: apiVisible field
|
||||||
|
return cb(undefined, { 'statistics': require('./equipment_statistics.json') });
|
||||||
|
};
|
||||||
|
|
||||||
|
const getOwned = (connection, id, cb) => {
|
||||||
|
let query = 'SELECT name, quantity FROM equipment WHERE accountId = ?;';
|
||||||
|
connection.query(query, [id], (err, results) => {
|
||||||
|
if (err) throw err;
|
||||||
|
|
||||||
|
let ret = {};
|
||||||
|
|
||||||
|
Object.keys(results).map((key) => {
|
||||||
|
if (ret[results[key].name] !== undefined) {
|
||||||
|
log('WARNING: Invalid database state, equipment owned', id, JSON.stringify(results));
|
||||||
|
}
|
||||||
|
ret[results[key].name] = results[key].quantity;
|
||||||
|
});
|
||||||
|
|
||||||
|
return cb(undefined, { 'owned': ret });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
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 (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: ${typeof(user)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
connection.query(query, [user], (err, results) => {
|
||||||
|
if (err) throw err;
|
||||||
|
|
||||||
|
if (results.length === 0) {
|
||||||
|
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;
|
||||||
|
return cb(undefined, true, results[0].username);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getStatistics: getStatistics,
|
||||||
|
getOwned: getOwned,
|
||||||
|
isAttacking: isAttacking
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user