Equipment buying and selling complete

This commit is contained in:
2019-06-01 23:02:08 +10:00
parent 7c7e69d4c5
commit 8161ebb512
8 changed files with 294 additions and 72 deletions
+8 -1
View File
@@ -21,7 +21,14 @@ let excluded = [ //messages that should not be logged
'News sent', 'News sent',
'Can\'t train while attacking', 'Can\'t train while attacking',
'Can\'t untrain while attacking' 'Can\'t untrain while attacking',
'Can\'t purchase while attacking',
'Can\'t sell while attacking',
'Purchase made',
'Sale made',
'Cleaned database'
]; ];
const log = (msg, ...args) => { const log = (msg, ...args) => {
+266 -66
View File
@@ -4,95 +4,295 @@ require('dotenv').config();
//utilities //utilities
let { log } = require('../common/utilities.js'); let { log } = require('../common/utilities.js');
const statistics = (connection, req, res, cb) => { let { isAttacking } = require('./combat.js');
const getStatistics = (cb) => {
//TODO: apiVisible field
return cb(undefined, { 'statistics': require('./equipment_statistics.json') }); return cb(undefined, { 'statistics': require('./equipment_statistics.json') });
}; };
const owned = (connection, req, res, cb) => { 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) => {
//validate the credentials //validate the credentials
let query = 'SELECT COUNT(*) AS total FROM sessions WHERE accountId = ? AND token = ?;'; let query = 'SELECT COUNT(*) AS total FROM sessions WHERE accountId = ? AND token = ?;';
connection.query(query, [req.body.id, req.body.token], (err, results) => { connection.query(query, [req.body.id, req.body.token], (err, results) => {
if (err) throw err; if (err) throw err;
if (results[0].total !== 1) { if (results[0].total !== 1) {
return cb('Invalid equipment owned credentials'); res.status(400).write(log('Invalid equipment credentials', req.body.id, req.body.token));
res.end();
return;
} }
let query = 'SELECT name, quantity FROM equipment WHERE accountId = ?;'; //if no field received, send everything
connection.query(query, [req.body.id], (err, results) => { if (!req.body.field) {
if (err) throw err; //compose the returned objects
return getStatistics((err, statisticsObj) => {
let res = {}
Object.keys(results).map((key) => {
if (res[results[key].name] !== undefined) {
log('WARNING: Invalid database state, equipment owned', JSON.stringify(results));
}
res[results[key].name] = results[key].quantity;
});
return cb(undefined, { 'owned': res });
});
});
};
const equipmentRequest = (connection) => (req, res) => {
//if no field received, send everything
if (!req.body.field) {
//compose the returned objects
statistics(connection, req, res, (err, statisticsObj) => {
if (err) {
res.status(400).write(log(err, req.body.id, req.body.token, req.body.field));
res.end();
return;
}
return owned(connection, req, res, (err, ownedObj) => {
if (err) { if (err) {
res.status(400).write(log(err, req.body.id, req.body.token, req.body.field)); res.status(400).write(log(err, req.body.id, req.body.token, req.body.field));
res.end(); res.end();
return; return;
} }
//finally, compose the resulting objects return getOwned(connection, req.body.id, (err, ownedObj) => {
res.status(200).json(Object.assign({}, statisticsObj, ownedObj)); if (err) {
res.end(); res.status(400).write(log(err, req.body.id, req.body.token, req.body.field));
res.end();
return;
}
//finally, compose the resulting objects
res.status(200).json(Object.assign({}, statisticsObj, ownedObj));
res.end();
});
}); });
}); }
return; //send specific fields
} switch(req.body.field) {
case 'statistics':
return getStatistics((err, obj) => {
if (err) {
res.status(400).write(log(err, req.body.id, req.body.token, req.body.field));
} else {
res.status(200).json(obj);
}
//send specific fields res.end();
switch(req.body.field) { });
case 'statistics':
return statistics(connection, req, res, (err, obj) => {
if (err) {
res.status(400).write(log(err, req.body.id, req.body.token, req.body.field));
} else {
res.status(200).json(obj);
}
case 'owned':
return getOwned(connection, req.body.id, (err, obj) => {
if (err) {
res.status(400).write(log(err, req.body.id, req.body.token, req.body.field));
} else {
res.status(200).json(obj);
}
res.end();
});
default:
res.status(400).write(log('Unknown field received', req.body.id, req.body.token, req.body.field));
res.end(); res.end();
}); }
});
case 'owned':
return owned(connection, req, res, (err, obj) => {
if (err) {
res.status(400).write(log(err, req.body.id, req.body.token, req.body.field));
} else {
res.status(200).json(obj);
}
res.end();
});
default:
res.status(400).write(log('Unknown field received', req.body.id, req.body.token, req.body.field));
res.end();
}
}; };
const purchaseRequest = (connection) => (req, res) => {
//validate the credentials
let query = 'SELECT COUNT(*) AS total FROM sessions WHERE accountId = ? AND token = ?;';
connection.query(query, [req.body.id, req.body.token], (err, credentials) => {
if (err) throw err;
if (credentials[0].total !== 1) {
res.status(400).write(log('Invalid equipment purchase credentials', JSON.stringify(body), body.id, body.token));
res.end();
return;
}
//no purchasing if you're attacking
isAttacking(connection, req.body.id, (err, attacking) => {
if (err) throw err;
if (attacking) {
res.status(400).write(log('Can\'t purchase while attacking', req.body.id, req.body. token, req.body.type, req.body.name));
res.end();
return;
}
//get the player's gold
let query = 'SELECT gold, scientists FROM profiles WHERE accountId = ?;';
connection.query(query, [req.body.id], (err, results) => {
if (err) throw err;
//just in case
if (results.length === 0) {
res.status(400).write(log('Purchase made on unrecognized account', req.body.id, req.body.token));
res.end();
return;
}
//get the stats for all objects
getStatistics((err, { statistics }) => {
if (err) throw err;
//valid parameters
if(!statistics[req.body.type] || !statistics[req.body.type][req.body.name]) {
res.status(400).write(log('Invalid equipment purchase parameters', req.body.id, req.body.token, req.body.type, req.body.name));
res.end();
return;
}
//enough gold?
if (results[0].gold < statistics[req.body.type][req.body.name].cost) {
res.status(400).write(log('Not enough gold', req.body.id, req.body.token, req.body.type, req.body.name));
res.end();
return;
}
//for sale?
if (!statistics[req.body.type][req.body.name].visible || !statistics[req.body.type][req.body.name].purchasable) {
res.status(400).write(log('Item not for sale', req.body.id, req.body.token, req.body.type, req.body.name));
res.end();
return;
}
//high enough level?
if (results[0].scientists < statistics[req.body.type][req.body.name].scientistsRequired) {
res.status(400).write(log('Not enough scientists', req.body.id, req.body.token, req.body.type, req.body.name));
res.end();
return;
}
//purchase approved.
//get the user's current item data (including quantity)
let query = 'SELECT * FROM equipment WHERE accountId = ? AND name = ?;';
connection.query(query, [req.body.id, req.body.name], (err, results) => {
if (err) throw err;
//add to or update the record
let query;
if (results.length > 0) {
query = 'UPDATE equipment SET quantity = quantity + 1 WHERE accountId = ? AND name = ? AND type = ?;';
} else {
query = 'INSERT INTO equipment (accountId, name, type, quantity) VALUES (?, ?, ?, 1);';
}
connection.query(query, [req.body.id, req.body.name, req.body.type], (err) => {
if (err) throw err;
//remove gold from the user's account
let query = 'UPDATE profiles SET gold = gold - ? WHERE accountId = ?;';
connection.query(query, [statistics[req.body.type][req.body.name].cost, req.body.id], (err) => {
if (err) throw err;
//return the new owned data
getOwned(connection, req.body.id, (err, results) => {
if (err) throw err;
res.status(200).json(Object.assign(results));
res.end();
log('Purchase made', req.body.id, req.body.token, req.body.type, req.body.name);
});
});
});
});
});
});
});
});
}
const sellRequest = (connection) => (req, res) => {
//validate the credentials
let query = 'SELECT COUNT(*) AS total FROM sessions WHERE accountId = ? AND token = ?;';
connection.query(query, [req.body.id, req.body.token], (err, credentials) => {
if (err) throw err;
if (credentials[0].total !== 1) {
res.status(400).write(log('Invalid equipment sell credentials', JSON.stringify(body), body.id, body.token));
res.end();
return;
}
//no selling if you're attacking
isAttacking(connection, req.body.id, (err, attacking) => {
if (err) throw err;
if (attacking) {
res.status(400).write(log('Can\'t sell while attacking', req.body.id, req.body. token, req.body.type, req.body.name));
res.end();
return;
}
//get the player's item quantity
let query = 'SELECT * FROM equipment WHERE accountId = ? AND type = ? AND name = ?;';
connection.query(query, [req.body.id, req.body.type, req.body.name], (err, results) => {
if (err) throw err;
if (results.length === 0) {
res.status(400).write(log('Can\'t sell something you don\'t own', req.body.id, req.body. token, req.body.type, req.body.name));
res.end();
return;
}
//get the stats for all objects
getStatistics((err, { statistics }) => {
if (err) throw err;
//valid parameters
if(!statistics[req.body.type] || !statistics[req.body.type][req.body.name]) {
res.status(400).write(log('Invalid equipment sell parameters', req.body.id, req.body.token, req.body.type, req.body.name));
res.end();
return;
}
//for sale?
if (!statistics[req.body.type][req.body.name].saleable) {
res.status(400).write(log('Item can\'t be sold', req.body.id, req.body.token, req.body.type, req.body.name));
res.end();
return;
}
//sale approved.
//add gold to the user's account
let query = 'UPDATE profiles SET gold = gold + ? WHERE accountId = ?;';
connection.query(query, [Math.floor(statistics[req.body.type][req.body.name].cost/2), req.body.id], (err) => {
if (err) throw err;
//remove the item from the inventory
let query = 'UPDATE equipment SET quantity = quantity - 1 WHERE id = ?;';
connection.query(query, [results[0].id], (err) => {
if (err) throw err;
//return the new owned data
getOwned(connection, req.body.id, (err, results) => {
if (err) throw err;
res.status(200).json(Object.assign(results));
res.end();
log('Sale made', req.body.id, req.body.token, req.body.type, req.body.name);
//Extra: clean the database
let query = 'DELETE FROM equipment WHERE quantity <= 0;';
connection.query(query, (err) => {
if (err) throw err;
log('Cleaned database', 'equipment sale');
});
});
})
});
});
});
});
});
}
module.exports = { module.exports = {
equipmentRequest: equipmentRequest equipmentRequest: equipmentRequest,
purchaseRequest: purchaseRequest,
sellRequest, sellRequest
}; };
+2
View File
@@ -49,6 +49,8 @@ combat.runCombatTick(connection);
let equipment = require('./equipment.js'); let equipment = require('./equipment.js');
app.post('/equipmentrequest', equipment.equipmentRequest(connection)); app.post('/equipmentrequest', equipment.equipmentRequest(connection));
app.post('/equipmentpurchaserequest', equipment.purchaseRequest(connection));
app.post('/equipmentsellrequest', equipment.sellRequest(connection));
//static directories //static directories
app.use('/styles', express.static(path.resolve(__dirname + '/../public/styles')) ); app.use('/styles', express.static(path.resolve(__dirname + '/../public/styles')) );
View File
+2 -1
View File
@@ -1 +1,2 @@
ALTER TABLE pastCombat CHANGE casualtiesVictor attackerCasualties INTEGER; #NOTE: ALWAYS, ALWAYS, ALWAYS write a script in revert.sql that undoes these changes
+2
View File
@@ -54,6 +54,8 @@ class CombatLog extends React.Component {
<p>{this.state.warning}</p> <p>{this.state.warning}</p>
</div> </div>
<h1 className='centered'>Combat Log</h1>
<ButtonHeader /> <ButtonHeader />
<PagedCombatLog <PagedCombatLog
setWarning={this.setWarning.bind(this)} setWarning={this.setWarning.bind(this)}
+5
View File
@@ -55,11 +55,16 @@ class Equipment extends React.Component {
<p>{this.state.warning}</p> <p>{this.state.warning}</p>
</div> </div>
<h1 className='centered'>Equipment</h1>
<p className='centered'>Equipment sells for half it's cost!</p>
<p className='centered'>Scientists: {this.props.scientists} - Gold: {this.props.gold}</p>
<EquipmentPanel <EquipmentPanel
getFetch={this.getFetch.bind(this)} getFetch={this.getFetch.bind(this)}
setWarning={this.setWarning.bind(this)} setWarning={this.setWarning.bind(this)}
scientists={this.props.scientists} scientists={this.props.scientists}
gold={this.props.gold} gold={this.props.gold}
onSuccess={ () => this.sendRequest('/profilerequest', {username: this.props.username}) }
/> />
</div> </div>
</div> </div>
+9 -4
View File
@@ -46,8 +46,8 @@ class Equipment extends React.Component {
<p className='col centered truncate'>{display[key].type}</p> <p className='col centered truncate'>{display[key].type}</p>
<p className='col centered truncate'>{display[key].owned}</p> <p className='col centered truncate'>{display[key].owned}</p>
<p className='col centered truncate'>{display[key].cost}</p> <p className='col centered truncate'>{display[key].cost}</p>
{display[key].purchasable ? <button className='col centered truncate' disabled={true || display[key].cost > this.props.gold}>+ Buy +</button> : <div className='col centered truncate' />} {display[key].purchasable ? <button className='col centered truncate' onClick={() => this.sendRequest('/equipmentpurchaserequest', { name: display[key].name, type: display[key].type }) } disabled={display[key].cost > this.props.gold}>+ Buy +</button> : <div className='col centered truncate' />}
{display[key].saleable ? <button className='col centered truncate' disabled={true || display[key].owned === 0}>- Sell -</button> : <div className='col centered truncate' />} {display[key].saleable ? <button className='col centered truncate' onClick={() => this.sendRequest('/equipmentsellrequest', { name: display[key].name, type: display[key].type }) } disabled={display[key].owned === 0}>- Sell -</button> : <div className='col centered truncate' />}
</div>)} </div>)}
</div> </div>
</div> </div>
@@ -67,9 +67,13 @@ class Equipment extends React.Component {
//on success //on success
this.setState(json); this.setState(json);
if (this.props.onSuccess) {
this.props.onSuccess(json);
}
} }
else if (xhr.status === 400 && this.props.setWarning) { else if (xhr.status === 400 && this.props.setWarning) {
this.setWarning(xhr.responseText); this.props.setWarning(xhr.responseText);
} }
} }
}; };
@@ -121,7 +125,8 @@ Equipment.propTypes = {
token: PropTypes.number.isRequired, token: PropTypes.number.isRequired,
setWarning: PropTypes.func, setWarning: PropTypes.func,
getFetch: PropTypes.func getFetch: PropTypes.func,
onSuccess: PropTypes.func
}; };
const mapStoreToProps = (store) => { const mapStoreToProps = (store) => {