Implemented stats page
This commit is contained in:
@@ -5,7 +5,7 @@ Major
|
|||||||
* Write the instructions for badges.
|
* Write the instructions for badges.
|
||||||
* Implement countdown timers for combat and training.
|
* Implement countdown timers for combat and training.
|
||||||
* Implement referral links.
|
* Implement referral links.
|
||||||
* Implement admin panel / stats page.
|
* Implement admin panel.
|
||||||
* Implement bug tracker.
|
* Implement bug tracker.
|
||||||
|
|
||||||
Minor
|
Minor
|
||||||
|
|||||||
+2
-2
@@ -8,7 +8,7 @@ let CronJob = require('cron').CronJob;
|
|||||||
let { logDiagnostics } = require('./diagnostics.js');
|
let { logDiagnostics } = require('./diagnostics.js');
|
||||||
let { log } = require('../common/utilities.js');
|
let { log } = require('../common/utilities.js');
|
||||||
|
|
||||||
let { getStatistics, isAttacking, logActivity } = require('./utilities.js');
|
let { getEquipmentStatistics, isAttacking, logActivity } = 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)
|
||||||
@@ -197,7 +197,7 @@ const runCombatTick = (connection) => {
|
|||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
|
|
||||||
//get the global equipment stats
|
//get the global equipment stats
|
||||||
getStatistics((err, { statistics }) => {
|
getEquipmentStatistics((err, { statistics }) => {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
|
|
||||||
//get the combat boosts from equipment, from highest to lowest
|
//get the combat boosts from equipment, from highest to lowest
|
||||||
|
|||||||
+5
-5
@@ -4,7 +4,7 @@ require('dotenv').config();
|
|||||||
//utilities
|
//utilities
|
||||||
let { log } = require('../common/utilities.js');
|
let { log } = require('../common/utilities.js');
|
||||||
|
|
||||||
let { getStatistics, getOwned, isAttacking, isSpying, logActivity } = require('./utilities.js');
|
let { getEquipmentStatistics, getOwned, isAttacking, isSpying, logActivity } = require('./utilities.js');
|
||||||
|
|
||||||
const equipmentRequest = (connection) => (req, res) => {
|
const equipmentRequest = (connection) => (req, res) => {
|
||||||
//validate the credentials
|
//validate the credentials
|
||||||
@@ -21,7 +21,7 @@ const equipmentRequest = (connection) => (req, res) => {
|
|||||||
//if no field received, send everything
|
//if no field received, send everything
|
||||||
if (!req.body.field) {
|
if (!req.body.field) {
|
||||||
//compose the returned objects
|
//compose the returned objects
|
||||||
return getStatistics((err, statisticsObj) => {
|
return getEquipmentStatistics((err, statisticsObj) => {
|
||||||
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();
|
||||||
@@ -45,7 +45,7 @@ const equipmentRequest = (connection) => (req, res) => {
|
|||||||
//send specific fields
|
//send specific fields
|
||||||
switch(req.body.field) {
|
switch(req.body.field) {
|
||||||
case 'statistics':
|
case 'statistics':
|
||||||
return getStatistics((err, obj) => {
|
return getEquipmentStatistics((err, obj) => {
|
||||||
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));
|
||||||
} else {
|
} else {
|
||||||
@@ -117,7 +117,7 @@ const purchaseRequest = (connection) => (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//get the stats for all objects
|
//get the stats for all objects
|
||||||
getStatistics((err, { statistics }) => {
|
getEquipmentStatistics((err, { statistics }) => {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
|
|
||||||
//valid parameters
|
//valid parameters
|
||||||
@@ -235,7 +235,7 @@ const sellRequest = (connection) => (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//get the stats for all objects
|
//get the stats for all objects
|
||||||
getStatistics((err, { statistics }) => {
|
getEquipmentStatistics((err, { statistics }) => {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
|
|
||||||
//valid parameters
|
//valid parameters
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ let connection = connectToDatabase(); //uses .env
|
|||||||
let diagnostics = require('./diagnostics.js');
|
let diagnostics = require('./diagnostics.js');
|
||||||
diagnostics.runDailyDiagnostics(connection);
|
diagnostics.runDailyDiagnostics(connection);
|
||||||
|
|
||||||
|
//game statistics
|
||||||
|
let statistics = require('./statistics.js');
|
||||||
|
app.post('/statisticsrequest', statistics.statisticsRequest(connection));
|
||||||
|
|
||||||
//handle accounts
|
//handle accounts
|
||||||
let accounts = require('./accounts.js');
|
let accounts = require('./accounts.js');
|
||||||
app.post('/signuprequest', accounts.signupRequest(connection));
|
app.post('/signuprequest', accounts.signupRequest(connection));
|
||||||
|
|||||||
+3
-3
@@ -8,7 +8,7 @@ let CronJob = require('cron').CronJob;
|
|||||||
let { logDiagnostics } = require('./diagnostics.js');
|
let { logDiagnostics } = require('./diagnostics.js');
|
||||||
let { log } = require('../common/utilities.js');
|
let { log } = require('../common/utilities.js');
|
||||||
|
|
||||||
let { getStatistics, isSpying, isAttacking, logActivity } = require('./utilities.js'); //TODO: rename getStatistics to getEquipmentStatistics
|
let { getEquipmentStatistics, isSpying, isAttacking, logActivity } = require('./utilities.js');
|
||||||
|
|
||||||
const spyRequest = (connection) => (req, res) => {
|
const spyRequest = (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)
|
||||||
@@ -292,7 +292,7 @@ const spyStealEquipmentInner = (connection, attackerId, defenderId, attackingUni
|
|||||||
connection.query(query, [defenderId], (err, results) => {
|
connection.query(query, [defenderId], (err, results) => {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
|
|
||||||
getStatistics((err, { statistics }) => {
|
getEquipmentStatistics((err, { statistics }) => {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
|
|
||||||
//don't steal certain items
|
//don't steal certain items
|
||||||
@@ -349,7 +349,7 @@ const spyStealEquipmentInner = (connection, attackerId, defenderId, attackingUni
|
|||||||
};
|
};
|
||||||
|
|
||||||
const removeForEachSoldier = (results, soldiers, cb) => {
|
const removeForEachSoldier = (results, soldiers, cb) => {
|
||||||
getStatistics((err, { statistics }) => {
|
getEquipmentStatistics((err, { statistics }) => {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
|
|
||||||
results.sort((a, b) => statistics[a.type][a.name].combatBoost < statistics[b.type][b.name].combatBoost);
|
results.sort((a, b) => statistics[a.type][a.name].combatBoost < statistics[b.type][b.name].combatBoost);
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
//environment variables
|
||||||
|
require('dotenv').config();
|
||||||
|
|
||||||
|
const round = (x) => Math.round(x * 100) / 100;
|
||||||
|
|
||||||
|
const statisticsRequest = (connection) => (req, res) => {
|
||||||
|
let query = 'SELECT COUNT(*) AS playerCount, SUM(gold) / COUNT(*) AS goldAverage FROM profiles;';
|
||||||
|
connection.query(query, (err, results) => {
|
||||||
|
if (err) throw err;
|
||||||
|
|
||||||
|
let playerCount = results[0].playerCount;
|
||||||
|
let goldAverage = results[0].goldAverage;
|
||||||
|
|
||||||
|
//determine the correct tick rate based on the current gold average
|
||||||
|
//NOTE: copy/pasted
|
||||||
|
let tickRate = (() => {
|
||||||
|
if (results[0].goldAverage < 120) return 5;
|
||||||
|
if (results[0].goldAverage < 130) return 15;
|
||||||
|
if (results[0].goldAverage < 140) return 30;
|
||||||
|
return 60; //slow it way down
|
||||||
|
})();
|
||||||
|
|
||||||
|
let nextTick = tickRate - (new Date()).getMinutes() % tickRate;
|
||||||
|
|
||||||
|
let query = 'SELECT COUNT(*) AS activity FROM accounts WHERE lastActivityTime >= DATE_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 DAY);';
|
||||||
|
connection.query(query, (err, results) => {
|
||||||
|
if (err) throw err;
|
||||||
|
|
||||||
|
let activity = results[0].activity;
|
||||||
|
let activePercentage = round(activity / playerCount * 100);
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
'Player Count': playerCount,
|
||||||
|
'Active Players': activity,
|
||||||
|
'Active Percentage': { string: `${activePercentage}%`, color: activePercentage >= 10 ? 'lightgreen' : activePercentage >= 5 ? 'yellow' : 'red'},
|
||||||
|
'Gold Average': `${round(goldAverage)}`,
|
||||||
|
'Tick Rate': `${tickRate} minutes`,
|
||||||
|
'Next Tick': `${nextTick} minute${nextTick === 1 ? '' : 's'} from now`
|
||||||
|
});
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
statisticsRequest: statisticsRequest
|
||||||
|
};
|
||||||
+2
-2
@@ -4,7 +4,7 @@ require('dotenv').config();
|
|||||||
//utilities
|
//utilities
|
||||||
let { log } = require('../common/utilities.js');
|
let { log } = require('../common/utilities.js');
|
||||||
|
|
||||||
const getStatistics = (cb) => {
|
const getEquipmentStatistics = (cb) => {
|
||||||
//TODO: apiVisible field
|
//TODO: apiVisible field
|
||||||
return cb(undefined, { 'statistics': require('./equipment_statistics.json') });
|
return cb(undefined, { 'statistics': require('./equipment_statistics.json') });
|
||||||
};
|
};
|
||||||
@@ -94,7 +94,7 @@ const logActivity = (connection, id) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getStatistics: getStatistics,
|
getEquipmentStatistics: getEquipmentStatistics,
|
||||||
getOwned: getOwned,
|
getOwned: getOwned,
|
||||||
isAttacking: isAttacking,
|
isAttacking: isAttacking,
|
||||||
isSpying: isSpying,
|
isSpying: isSpying,
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ export default class App extends React.Component {
|
|||||||
<LazyRoute path='/patronlist' component={() => import('./pages/patron_list.jsx')} />
|
<LazyRoute path='/patronlist' component={() => import('./pages/patron_list.jsx')} />
|
||||||
<LazyRoute path='/news/:postId' component={() => import('./pages/news.jsx')} />
|
<LazyRoute path='/news/:postId' component={() => import('./pages/news.jsx')} />
|
||||||
<LazyRoute path='/rules' component={() => import('./pages/rules.jsx')} />
|
<LazyRoute path='/rules' component={() => import('./pages/rules.jsx')} />
|
||||||
|
<LazyRoute path='/statistics' component={() => import('./pages/statistics.jsx')} />
|
||||||
|
|
||||||
<LazyRoute path='*' component={() => import('./pages/page_not_found.jsx')} />
|
<LazyRoute path='*' component={() => import('./pages/page_not_found.jsx')} />
|
||||||
</Switch>
|
</Switch>
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
//panels
|
||||||
|
import CommonLinks from '../panels/common_links.jsx';
|
||||||
|
import StatisticsPanel from '../panels/statistics.jsx';
|
||||||
|
|
||||||
|
class Statistics extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
warning: '', //TODO: unified warning?
|
||||||
|
fetch: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||||
|
this.state.fetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let warningStyle = {
|
||||||
|
display: this.state.warning.length > 0 ? 'flex' : 'none'
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='page'>
|
||||||
|
<div className='sidePanelPage'>
|
||||||
|
<div className='sidePanel'>
|
||||||
|
<CommonLinks />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mainPanel'>
|
||||||
|
<div className='warning' style={warningStyle}>
|
||||||
|
<p>{this.state.warning}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 className='centered'>Game Statistics</h1>
|
||||||
|
<StatisticsPanel setWarning={this.setWarning.bind(this)} getFetch={ (fn) => this.setState({ fetch: fn }) } />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
setWarning(s) {
|
||||||
|
this.setState({ warning: s });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Statistics;
|
||||||
@@ -37,6 +37,7 @@ class CommonLinks extends React.Component {
|
|||||||
<p className='mobile centered'><Link to='/tasklist' onClick={this.props.onClickTaskList}>Task List</Link></p>
|
<p className='mobile centered'><Link to='/tasklist' onClick={this.props.onClickTaskList}>Task List</Link></p>
|
||||||
<p className='mobile centered'><Link to='/patronlist' onClick={this.props.onClickPatronList}>Patron List</Link></p>
|
<p className='mobile centered'><Link to='/patronlist' onClick={this.props.onClickPatronList}>Patron List</Link></p>
|
||||||
<p className='mobile centered'><Link to='/rules' onClick={this.props.onClickRules}>Rules</Link></p>
|
<p className='mobile centered'><Link to='/rules' onClick={this.props.onClickRules}>Rules</Link></p>
|
||||||
|
<p className='mobile centered'><Link to='/statistics' onClick={this.props.onClickStatistics}>Game Stats</Link></p>
|
||||||
|
|
||||||
<Extra />
|
<Extra />
|
||||||
|
|
||||||
@@ -53,6 +54,7 @@ class CommonLinks extends React.Component {
|
|||||||
<p className='mobile centered'><Link to='/tasklist' onClick={this.props.onClickTaskList}>Task List</Link></p>
|
<p className='mobile centered'><Link to='/tasklist' onClick={this.props.onClickTaskList}>Task List</Link></p>
|
||||||
<p className='mobile centered'><Link to='/patronlist' onClick={this.props.onClickPatronList}>Patron List</Link></p>
|
<p className='mobile centered'><Link to='/patronlist' onClick={this.props.onClickPatronList}>Patron List</Link></p>
|
||||||
<p className='mobile centered'><Link to='/rules' onClick={this.props.onClickRules}>Rules</Link></p>
|
<p className='mobile centered'><Link to='/rules' onClick={this.props.onClickRules}>Rules</Link></p>
|
||||||
|
<p className='mobile centered'><Link to='/statistics' onClick={this.props.onClickStatistics}>Game Stats</Link></p>
|
||||||
|
|
||||||
<Extra />
|
<Extra />
|
||||||
</div>
|
</div>
|
||||||
@@ -75,7 +77,8 @@ CommonLinks.propTypes = {
|
|||||||
onClickSpyingLog: PropTypes.func,
|
onClickSpyingLog: PropTypes.func,
|
||||||
onClickTaskList: PropTypes.func,
|
onClickTaskList: PropTypes.func,
|
||||||
onClickPatronList: PropTypes.func,
|
onClickPatronList: PropTypes.func,
|
||||||
onClickRules: PropTypes.func
|
onClickRules: PropTypes.func,
|
||||||
|
onClickStatistics: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
function mapStoreToProps(store) {
|
function mapStoreToProps(store) {
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
class Statistics extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
data: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (props.getFetch) {
|
||||||
|
props.getFetch(() => this.sendRequest('/statisticsrequest'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className='panel table noCollapse'>
|
||||||
|
{Object.keys(this.state.data).map((key) => <div key={key} className='row'>
|
||||||
|
<p className='col'>{key}:</p>
|
||||||
|
<p className='col'>{typeof(this.state.data[key]) === 'object' ? <span style={{color: this.state.data[key].color}}>{this.state.data[key].string}</span> : <span>{this.state.data[key]}</span>}</p>
|
||||||
|
<div className='col mobile hide' />
|
||||||
|
</div>)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendRequest(url, args = {}) { //send a unified request, using my credentials
|
||||||
|
//TODO: move sendRequest() into it's own module
|
||||||
|
//build the XHR
|
||||||
|
let xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('POST', url, true);
|
||||||
|
|
||||||
|
xhr.onreadystatechange = () => {
|
||||||
|
if (xhr.readyState === 4) {
|
||||||
|
if (xhr.status === 200) {
|
||||||
|
let json = JSON.parse(xhr.responseText);
|
||||||
|
|
||||||
|
//on success
|
||||||
|
this.setState({ data: json });
|
||||||
|
}
|
||||||
|
else if (xhr.status === 400 && this.props.setWarning) {
|
||||||
|
this.props.setWarning(xhr.responseText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
|
||||||
|
xhr.send(JSON.stringify({
|
||||||
|
//NOTE: No id or token needed for statistics
|
||||||
|
...args
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Statistics.propTypes = {
|
||||||
|
setWarning: PropTypes.func,
|
||||||
|
getFetch: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Statistics;
|
||||||
Reference in New Issue
Block a user