diff --git a/server/equipment.js b/server/equipment.js index c524033..7efec91 100644 --- a/server/equipment.js +++ b/server/equipment.js @@ -4,44 +4,95 @@ require('dotenv').config(); //utilities let { log } = require('../common/utilities.js'); -//the statistics -let equipmentStatistics = require('./equipment_statistics.json'); +const statistics = (connection, req, res, cb) => { + return cb(undefined, { 'statistics': require('./equipment_statistics.json') }); +} -const statisticsRequest = () => (req, res) => { - res.status(200).json(equipmentStatistics); - res.end(); -}; - -//TODO: incomplete - -const listRequest = (connection) => (req, res) => { - //verify identity - let query = 'SELECT accountId FROM sessions WHERE accountId IN (SELECT id FROM accounts WHERE username = ?) AND token = ?;'; - connection.query(query, [req.body.username, req.body.token], (err, results) => { +const owned = (connection, req, res, cb) => { + //verify the credentials + let query = 'SELECT COUNT(*) AS total FROM sessions WHERE accountId = ? AND token = ?;'; + connection.query(query, [req.body.id, req.body.token], (err, results) => { if (err) throw err; - let query = 'SELECT name, quantity, type FROM equipment WHERE accountId = ?;'; - connection.query(query, [results[0].accountId], (err, results) => { + if (results[0].total !== 1) { + return cb('Invalid equipment owned credentials'); + } + + let query = 'SELECT name, quantity FROM equipment WHERE accountId = ?;'; + connection.query(query, [req.body.id], (err, results) => { if (err) throw err; - //transform the results into a sendable array - let list = {}; + let res = {} - results.map((record) => { - //initialize this type - list[record.type] = list[record.type] || {}; - - //send the quantity of every type - list[record.type][record.name] = record.quantity; + 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; }); - res.status(200).json(list); - res.end(); + 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) { + 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 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); + } + + 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(); + } +} module.exports = { - statisticsRequest: statisticsRequest, - listRequest: listRequest + equipmentRequest: equipmentRequest } \ No newline at end of file diff --git a/server/equipment_statistics.json b/server/equipment_statistics.json index 18ed71f..fe2939b 100644 --- a/server/equipment_statistics.json +++ b/server/equipment_statistics.json @@ -1,15 +1,15 @@ { "Weapons": { - "Stick": { "cost": 50, "combatBoost": 0.02, "scientistsRequired": 1 }, - "Dagger": { "cost": 75, "combatBoost": 0.03, "scientistsRequired": 2 }, - "Sword": { "cost": 100, "combatBoost": 0.04, "scientistsRequired": 3 }, - "Longsword": { "cost": 150, "combatBoost": 0.05, "scientistsRequired": 4 }, - "Frying Pan": { "cost": 200, "combatBoost": 0.06, "scientistsRequired": 5 } + "Stick": { "cost": 50, "combatBoost": 0.02, "scientistsRequired": 1, "purchasable": true }, + "Dagger": { "cost": 75, "combatBoost": 0.03, "scientistsRequired": 2, "purchasable": true }, + "Sword": { "cost": 100, "combatBoost": 0.04, "scientistsRequired": 3, "purchasable": true }, + "Longsword": { "cost": 150, "combatBoost": 0.05, "scientistsRequired": 4, "purchasable": true }, + "Frying Pan": { "cost": 200, "combatBoost": 0.06, "scientistsRequired": 5, "purchasable": true } }, "Armour": { - "Leather": { "cost": 75, "combatBoost": 0.02, "scientistsRequired": 2 }, - "Gambeson": { "cost": 100, "combatBoost": 0.03, "scientistsRequired": 3 }, - "Chainmail": { "cost": 150, "combatBoost": 0.04, "scientistsRequired": 4 }, - "Platemail": { "cost": 200, "combatBoost": 0.05, "scientistsRequired": 5 } + "Leather": { "cost": 75, "combatBoost": 0.02, "scientistsRequired": 2, "purchasable": true }, + "Gambeson": { "cost": 100, "combatBoost": 0.03, "scientistsRequired": 3, "purchasable": true }, + "Chainmail": { "cost": 150, "combatBoost": 0.04, "scientistsRequired": 4, "purchasable": true }, + "Platemail": { "cost": 200, "combatBoost": 0.05, "scientistsRequired": 5, "purchasable": true } } } \ No newline at end of file diff --git a/server/index.js b/server/index.js index 3494d92..48709c8 100644 --- a/server/index.js +++ b/server/index.js @@ -47,9 +47,8 @@ app.post('/attackstatusrequest', combat.attackStatusRequest(connection)); app.post('/combatlogrequest', combat.combatLogRequest(connection)); combat.runCombatTick(connection); -//let equipment = require('./equipment.js'); -//app.post('/equipmentstatisticsrequest', equipment.statisticsRequest()); -//app.post('/equipmentlistrequest', equipment.listRequest(connection)); +let equipment = require('./equipment.js'); +app.post('/equipmentrequest', equipment.equipmentRequest(connection)); //static directories app.use('/styles', express.static(path.resolve(__dirname + '/../public/styles')) ); diff --git a/src/components/app.jsx b/src/components/app.jsx index 4c82b4e..3bb35ae 100644 --- a/src/components/app.jsx +++ b/src/components/app.jsx @@ -69,6 +69,7 @@ export default class App extends React.Component { import('./pages/profile.jsx')} /> import('./pages/ladder.jsx')} /> import('./pages/combat_log.jsx')} /> + import('./pages/equipment.jsx')} /> import('./pages/page_not_found.jsx')} /> diff --git a/src/components/pages/combat_log.jsx b/src/components/pages/combat_log.jsx index ccec432..73b7036 100644 --- a/src/components/pages/combat_log.jsx +++ b/src/components/pages/combat_log.jsx @@ -44,28 +44,26 @@ class CombatLog extends React.Component { let ButtonHeader = this.buttonHeader.bind(this); return ( -
-
-
- +
+
+ +
+ +
+
+

{this.state.warning}

-
-
-

{this.state.warning}

-
- - - - -
+ + +
@@ -126,7 +124,8 @@ class CombatLog extends React.Component { }; CombatLog.propTypes = { - username: PropTypes.string.isRequired + username: PropTypes.string.isRequired, + loggedIn: PropTypes.bool.isRequired }; const mapStoreToProps = (store) => { diff --git a/src/components/pages/equipment.jsx b/src/components/pages/equipment.jsx new file mode 100644 index 0000000..9c9517a --- /dev/null +++ b/src/components/pages/equipment.jsx @@ -0,0 +1,135 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; + +//actions +import { storeScientists, storeGold, clearProfile } from '../../actions/profile.js'; + +//panels +import CommonLinks from '../panels/common_links.jsx'; +import EquipmentPanel from '../panels/equipment.jsx'; + +class Equipment extends React.Component { + constructor(props) { + super(props); + + this.state = { + fetch: null, + + warning: '' + }; + + this.sendRequest('/profilerequest', {username: this.props.username}); + } + + componentDidMount() { + if (!this.props.loggedIn) { + this.props.history.replace('/login'); + } + } + + componentWillUnmount() { + this.props.clearProfile(); + } + + componentDidUpdate(prevProps, prevState, snapshot) { + if (JSON.stringify(this.state) !== JSON.stringify(prevState)) { + this.state.fetch(); + this.sendRequest('/profilerequest', {username: this.props.username}); + } + } + + render() { + let warningStyle = { + display: this.state.warning.length > 0 ? 'flex' : 'none' + }; + + return ( +
+
+ +
+ +
+
+

{this.state.warning}

+
+ + +
+
+ ); + } + + //gameplay functions + sendRequest(url, args = {}) { //send a unified request, using my credentials + //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.props.storeScientists(json.scientists); + this.props.storeGold(json.gold); + } + else if (xhr.status === 400) { + this.setWarning(xhr.responseText); + } + } + }; + + xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); + xhr.send(JSON.stringify({ + id: this.props.id, + token: this.props.token, + ...args + })); + } + + //bound callbacks + getFetch(fn) { + this.setState({ fetch: fn }); + } + + setWarning(s) { + this.setState({ warning: s }); + } +}; + +Equipment.propTypes = { + username: PropTypes.string.isRequired, + loggedIn: PropTypes.bool.isRequired, + storeScientists: PropTypes.func.isRequired, + storeGold: PropTypes.func.isRequired, + clearProfile: PropTypes.func.isRequired +}; + +const mapStoreToProps = (store) => { + return { + username: store.account.username, + loggedIn: store.account.id !== 0, + scientists: store.profile.scientists, + gold: store.profile.gold + }; +}; + +const mapDispatchToProps = (dispatch) => { + return { + storeScientists: (x) => dispatch(storeScientists(x)), + storeGold: (x) => dispatch(storeGold(x)), + clearProfile: () => dispatch(clearProfile()) + }; +}; + +Equipment = connect(mapStoreToProps, mapDispatchToProps)(Equipment); + +export default Equipment; \ No newline at end of file diff --git a/src/components/panels/common_links.jsx b/src/components/panels/common_links.jsx index 13ce03c..6179c04 100644 --- a/src/components/panels/common_links.jsx +++ b/src/components/panels/common_links.jsx @@ -30,6 +30,7 @@ class CommonLinks extends React.Component {

Return Home

Your Kingdom

+

Your Equipment

Game Ladder

Combat Log

Change Password

diff --git a/src/components/panels/equipment.jsx b/src/components/panels/equipment.jsx index 48e0cc5..57924e5 100644 --- a/src/components/panels/equipment.jsx +++ b/src/components/panels/equipment.jsx @@ -1,114 +1,141 @@ import React from 'react'; +import { withRouter, Link } from 'react-router-dom'; +import { connect } from 'react-redux'; import PropTypes from 'prop-types'; -//TODO: incomplete - class Equipment extends React.Component { constructor(props) { super(props); + this.state = { - statistics: {}, - equipment: {} + // }; - if (this.props.getFetchStatistics) { -// this.props.getFetchStatistics(this.fetchStatistics.bind(this)); + if (this.props.getFetch) { + this.props.getFetch((field) => this.sendRequest('/equipmentrequest', {field: field} )); } - - this.fetchStatistics(); - - if (this.props.getFetchEquipment) { -// this.props.getFetchEquipment(this.fetchEquipmentList.bind(this)); - } - - this.fetchEquipmentList(); } render() { - //print the purchasable weapons, then purchasable armour, then stuff you can't buy - let statistics = JSON.parse(JSON.stringify(this.state.statistics)); + //if there are no scientists + if (this.props.scientists <= 0) { + return ( +
+

You have no scientists!

+

Go and train some!

+
+ ); + } - //filter out what you can't get at your current scientist count - Object.keys(statistics).forEach((typeKey) => { - Object.keys(statistics[typeKey]).forEach((nameKey) => { - if (statistics[typeKey][nameKey].scientists > this.props.scientists) { - delete statistics[typeKey][nameKey]; - } - if (Object.keys(statistics[typeKey]).length === 0) { - delete statistics[typeKey]; - } - }); - }); - - console.log(this.state.statistics); - console.log(statistics); + let display = this.flattenStructure(this.state, this.props.scientists); return (
-
-

Equipment Name

-

Equipment Type

-

Quantity

-

Cost

-

Buy

-

Sell

+

Name

+

Type

+

Owned

+

Cost

+

Buy

+

Sell

- - + {Object.keys(display).map((key) =>
+

{display[key].name}

+

{display[key].type}

+

{display[key].owned}

+

{display[key].cost}

+ + +
)}
); } - fetchStatistics() { + //gameplay functions + sendRequest(url, args = {}) { //send a unified request, using my credentials //build the XHR let xhr = new XMLHttpRequest(); + xhr.open('POST', url, true); xhr.onreadystatechange = () => { if (xhr.readyState === 4) { if (xhr.status === 200) { - let data = JSON.parse(xhr.responseText); - this.setState({ statistics: data }); + let json = JSON.parse(xhr.responseText); + + //on success + this.setState(json); + } + else if (xhr.status === 400 && this.props.setWarning) { + this.setWarning(xhr.responseText); } } - } + }; - xhr.open('POST', '/equipmentstatisticsrequest', true); - xhr.send(); - } - - fetchEquipmentList(username = this.props.username, token = this.props.token) { - //build the XHR - let xhr = new XMLHttpRequest(); - - xhr.onreadystatechange = () => { - if (xhr.readyState === 4) { - if (xhr.status === 200) { - let data = JSON.parse(xhr.responseText); - this.setState({ equipment: data }); - } - } - } - - xhr.open('POST', '/equipmentlistrequest', true); xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); xhr.send(JSON.stringify({ - username: username, - token: token + id: this.props.id, + token: this.props.token, + ...args })); } + + flattenStructure(structure, scientists) { + if (!structure || !structure.statistics) { + return []; + } + + let ret = []; //return value: ret[0] = { name: '', type: '', owned: 0, cost: 0 } + + Object.keys(structure.statistics).map((type) => { + Object.keys(structure.statistics[type]).map((name) => { + //don't render high level items + if (structure.statistics[type][name].scientistsRequired > scientists) { + return; + } + + //if you can't buy it and you down own it, don't render it (for legendary items) + if (!structure.statistics[type][name].purchasable && !structure.owned[name]) { + return; + } + + //finally + ret.push({ + name: name, + type: type, + owned: (structure.owned && structure.owned[name]) || 0, + cost: structure.statistics[type][name].cost + }); + }); + }); + + return ret; + } }; Equipment.propTypes = { - username: PropTypes.string.isRequired, + id: PropTypes.number.isRequired, token: PropTypes.number.isRequired, - scientists: PropTypes.number.isRequired, - getFetchStatistics: PropTypes.func, - getFetchEquipmentList: PropTypes.func + setWarning: PropTypes.func, + getFetch: PropTypes.func }; -export default Equipment; \ No newline at end of file +const mapStoreToProps = (store) => { + return { + id: store.account.id, + token: store.account.token + }; +}; + +const mapDispatchToProps = (dispatch) => { + return { + // + }; +}; + +Equipment = connect(mapStoreToProps, mapDispatchToProps)(Equipment); + +export default withRouter(Equipment); \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index 212c444..908c145 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -24,7 +24,7 @@ module.exports = { ] }, optimization: { - minimize: true, + minimize: false, minimizer: [ new TerserPlugin({ terserOptions: {