Finished spying system
This commit is contained in:
@@ -18,6 +18,7 @@ let excluded = [ //messages that should not be logged
|
||||
'idle',
|
||||
|
||||
'Combat log sent',
|
||||
'Spy log sent',
|
||||
'News sent',
|
||||
'News sent (singular)',
|
||||
|
||||
|
||||
+60
-4
@@ -103,9 +103,64 @@ const spyStatusRequest = (connection) => (req, res) => {
|
||||
};
|
||||
|
||||
const spyLogRequest = (connection) => (req, res) => {
|
||||
//TODO
|
||||
res.status(400).write(log('Not yet implemented', 'spyLogRequest'));
|
||||
res.end();
|
||||
//verify the user's 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;
|
||||
|
||||
if (results[0].total !== 1) {
|
||||
res.status(400).write(log('Invalid spy log credentials', req.body.id, req.body.token));
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
//grab the spying log and equipment stolen based on the id
|
||||
let query = 'SELECT pastSpying.id AS id, pastSpying.eventTime AS eventTime, pastSpying.attackerId AS attackerId, pastSpying.defenderId AS defenderId, atk.username AS attackerUsername, def.username AS defenderUsername, pastSpying.attackingUnits AS attackingUnits, pastSpying.success AS success, pastSpying.spoilsGold AS spoilsGold, equipmentStolen.name AS equipmentStolenName, equipmentStolen.type AS equipmentStolenType, equipmentStolen.quantity AS equipmentStolenQuantity FROM pastSpying LEFT JOIN equipmentStolen ON pastSpying.id = equipmentStolen.pastSpyingId LEFT JOIN accounts AS atk ON pastSpying.attackerId = atk.id LEFT JOIN accounts AS def ON pastSpying.defenderId = def.id WHERE pastSpying.attackerId = ? OR pastSpying.defenderId = ? ORDER BY eventTime DESC LIMIT ?, ?;';
|
||||
connection.query(query, [req.body.id, req.body.id, req.body.start, req.body.length], (err, results) => {
|
||||
if (err) throw err;
|
||||
|
||||
//build the sendable data structure (delete names from successful events when you're the losing defender, etc.)
|
||||
let ret = [];
|
||||
|
||||
results.forEach((result) => {
|
||||
//appending equipment stolen
|
||||
if (ret[result.id]) {
|
||||
ret[result.id].equipmentStolen.push({
|
||||
name: result.equipmentStolenName,
|
||||
type: result.equipmentStolenType,
|
||||
quantity: result.equipmentStolenQuantity
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let hideData = req.body.id === result.defenderId && result.success === 'success';
|
||||
|
||||
//creating a new entry
|
||||
ret[result.id] = {
|
||||
eventTime: result.eventTime,
|
||||
attacker: hideData ? null : result.attackerUsername,
|
||||
defender: result.defenderUsername,
|
||||
attackingUnits: hideData ? null : result.attackingUnits,
|
||||
success: result.success,
|
||||
spoilsGold: result.spoilsGold,
|
||||
equipmentStolen: result.equipmentStolenName ? [{
|
||||
name: result.equipmentStolenName,
|
||||
type: result.equipmentStolenType,
|
||||
quantity: result.equipmentStolenQuantity
|
||||
}] : []
|
||||
};
|
||||
});
|
||||
|
||||
//remove null fields
|
||||
ret = ret.filter(x => x);
|
||||
|
||||
//send the build structure
|
||||
res.status(200).json(ret);
|
||||
res.end();
|
||||
|
||||
log('Spy log sent', JSON.stringify(ret));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const runSpyTick = (connection) => {
|
||||
@@ -157,7 +212,7 @@ const spyGameplayLogic = (connection, pendingSpying) => {
|
||||
//more spies reduces the chances of being seen? Counter intuitive
|
||||
let chanceSeen = totalEyes / (pendingSpying.attackingUnits * 10); //it takes 10 eyes to guarantee the capture of 1 spy, 50% chance to capture 2 spies, etc.
|
||||
|
||||
//if seen
|
||||
//if seen (failure)
|
||||
if (Math.random() <= 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) => {
|
||||
@@ -458,3 +513,4 @@ module.exports = {
|
||||
runSpyTick: runSpyTick
|
||||
};
|
||||
|
||||
//TODO: move balance variables to an external file (.env?)
|
||||
@@ -67,9 +67,10 @@ export default class App extends React.Component {
|
||||
<LazyRoute path='/passwordreset' component={() => import('./pages/password_reset.jsx')} />
|
||||
|
||||
<LazyRoute path='/profile' component={() => import('./pages/profile.jsx')} />
|
||||
<LazyRoute path='/equipment' component={() => import('./pages/equipment.jsx')} />
|
||||
<LazyRoute path='/ladder' component={() => import('./pages/ladder.jsx')} />
|
||||
<LazyRoute path='/combatlog' component={() => import('./pages/combat_log.jsx')} />
|
||||
<LazyRoute path='/equipment' component={() => import('./pages/equipment.jsx')} />
|
||||
<LazyRoute path='/spyinglog' component={() => import('./pages/spying_log.jsx')} />
|
||||
|
||||
<LazyRoute path='/tasklist' component={() => import('./pages/task_list.jsx')} />
|
||||
<LazyRoute path='/patronlist' component={() => import('./pages/patron_list.jsx')} />
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import queryString from 'query-string';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
//panels
|
||||
import CommonLinks from '../panels/common_links.jsx';
|
||||
import PagedSpyingLog from '../panels/paged_spying_log.jsx';
|
||||
|
||||
class SpyingLog extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
let params = queryString.parse(props.location.search);
|
||||
|
||||
this.state = {
|
||||
params: params,
|
||||
start: parseInt(params.log) || 0,
|
||||
length: parseInt(params.length) || 20,
|
||||
|
||||
fetch: null,
|
||||
|
||||
warning: ''
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (!this.props.loggedIn) {
|
||||
this.props.history.replace('/login');
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||
if (JSON.stringify(this.state) !== JSON.stringify(prevState)) {
|
||||
this.state.fetch();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let warningStyle = {
|
||||
display: this.state.warning.length > 0 ? 'flex' : 'none'
|
||||
};
|
||||
|
||||
let ButtonHeader = this.buttonHeader.bind(this);
|
||||
|
||||
return (
|
||||
<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'>Espionage Log</h1>
|
||||
|
||||
<ButtonHeader />
|
||||
<PagedSpyingLog
|
||||
setWarning={this.setWarning.bind(this)}
|
||||
username={this.props.username}
|
||||
start={this.state.start}
|
||||
length={this.state.length}
|
||||
getFetch={this.getFetch.bind(this)}
|
||||
onReceived={this.onReceived.bind(this)}
|
||||
/>
|
||||
<ButtonHeader />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
buttonHeader() {
|
||||
return (
|
||||
<div className='table noCollapse'>
|
||||
<div className='row'>
|
||||
<button className='col' onClick={ this.decrement.bind(this) }>{'< Back'}</button>
|
||||
<div className='col hide mobile' />
|
||||
<div className='col hide mobile' />
|
||||
<button className='col' onClick={ this.increment.bind(this) }>{'Next >'}</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
increment() {
|
||||
let start = this.state.start + this.state.length;
|
||||
|
||||
this.props.history.push(`${this.props.location.pathname}?log=${start}`);
|
||||
}
|
||||
|
||||
decrement() {
|
||||
let start = Math.max(0, this.state.start - this.state.length);
|
||||
|
||||
//don't decrement too far
|
||||
if (start === this.state.start) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.history.push(`${this.props.location.pathname}?log=${start}`);
|
||||
}
|
||||
|
||||
//bound callbacks
|
||||
getFetch(fn) {
|
||||
this.setState({ fetch: fn });
|
||||
}
|
||||
|
||||
onReceived(data) {
|
||||
if (data.length === 0) {
|
||||
let start = Math.max(0, this.state.start - this.state.length);
|
||||
|
||||
//don't decrement too far
|
||||
if (start === this.state.start) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.history.replace(`${this.props.location.pathname}?log=${start}`);
|
||||
}
|
||||
}
|
||||
|
||||
setWarning(s) {
|
||||
this.setState({ warning: s });
|
||||
}
|
||||
};
|
||||
|
||||
SpyingLog.propTypes = {
|
||||
username: PropTypes.string.isRequired,
|
||||
loggedIn: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
const mapStoreToProps = (store) => {
|
||||
return {
|
||||
username: store.account.username,
|
||||
loggedIn: store.account.id !== 0
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
//
|
||||
};
|
||||
};
|
||||
|
||||
SpyingLog = connect(mapStoreToProps, mapDispatchToProps)(SpyingLog);
|
||||
|
||||
export default SpyingLog;
|
||||
@@ -32,6 +32,7 @@ class CommonLinks extends React.Component {
|
||||
<p className='mobile centered'><Link to='/equipment' onClick={this.props.onClickEquipment}>Your Equipment</Link></p>
|
||||
<p className='mobile centered'><Link to='/ladder' onClick={this.props.onClickLadder}>Attack (Game Ladder)</Link></p>
|
||||
<p className='mobile centered'><Link to='/combatlog' onClick={this.props.onClickCombatLog}>Combat Log</Link></p>
|
||||
<p className='mobile centered'><Link to='/spyinglog' onClick={this.props.onClickSpyingLog}>Espionage Log</Link></p>
|
||||
<p className='mobile centered'><Link to='/passwordchange' onClick={this.props.onClickPasswordChange}>Change Password</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>
|
||||
@@ -71,6 +72,7 @@ CommonLinks.propTypes = {
|
||||
onClickEquipment: PropTypes.func,
|
||||
onClickLadder: PropTypes.func,
|
||||
onClickCombatLog: PropTypes.func,
|
||||
onClickSpyingLog: PropTypes.func,
|
||||
onClickTaskList: PropTypes.func,
|
||||
onClickPatronList: PropTypes.func,
|
||||
onClickRules: PropTypes.func
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
import React from 'react';
|
||||
import { withRouter, Link } from 'react-router-dom';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import SpyingLogRecord from './spying_log_record.jsx';
|
||||
|
||||
class PagedSpyingLog extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
//
|
||||
};
|
||||
|
||||
if (props.getFetch) {
|
||||
props.getFetch(() => this.sendRequest('/spylogrequest', {start: props.start, length: props.length}));
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{Object.keys(this.state).map((key) => <SpyingLogRecord key={key} username={this.props.username} {...this.state[key]} />)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
//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.setState(json);
|
||||
|
||||
if (this.props.onReceived) {
|
||||
this.props.onReceived(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({
|
||||
id: this.props.id,
|
||||
token: this.props.token,
|
||||
...args
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
PagedSpyingLog.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
token: PropTypes.number.isRequired,
|
||||
|
||||
username: PropTypes.string.isRequired,
|
||||
start: PropTypes.number.isRequired,
|
||||
length: PropTypes.number.isRequired,
|
||||
|
||||
setWarning: PropTypes.func,
|
||||
getFetch: PropTypes.func,
|
||||
onReceived: PropTypes.func
|
||||
};
|
||||
|
||||
const mapStoreToProps = (store) => {
|
||||
return {
|
||||
id: store.account.id,
|
||||
token: store.account.token
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
//
|
||||
};
|
||||
};
|
||||
|
||||
PagedSpyingLog = connect(mapStoreToProps, mapDispatchToProps)(PagedSpyingLog);
|
||||
|
||||
export default withRouter(PagedSpyingLog);
|
||||
@@ -0,0 +1,78 @@
|
||||
import React from 'react';
|
||||
import { withRouter, Link } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
class SpyingLogRecord extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
//
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className='table noCollapse'>
|
||||
<hr />
|
||||
<div className='break' />
|
||||
<div className='row'>
|
||||
<p className='col truncate'>{this.parseDate(this.props.eventTime)}</p>
|
||||
<p className='col truncate'>Atk: {this.prettyName(this.props.attacker)} ({this.props.attackingUnits ? this.props.attackingUnits : '???'} units)</p>
|
||||
<p className='col truncate'>Def: {this.prettyName(this.props.defender)}</p>
|
||||
</div>
|
||||
|
||||
<div className='row'>
|
||||
<p className='col truncate'><span className='mobile hide'>Result: </span>{this.capitalizeFirstLetter(this.props.success)}</p>
|
||||
<p className='col truncate'>Gold Stolen: {this.props.spoilsGold}</p>
|
||||
<div className='col' />
|
||||
</div>
|
||||
|
||||
{Object.keys(this.props.equipmentStolen).map((key) => {
|
||||
return (
|
||||
<div key={key} className='row'>
|
||||
<p className='col truncate'><span className='mobile hide' style={{color:'red'}}>Stolen: </span>{this.props.equipmentStolen[key].name}</p>
|
||||
<p className='col truncate'>{this.props.equipmentStolen[key].type}</p>
|
||||
<p className='col truncate'><span className='mobile hide'>Total: </span>{this.props.equipmentStolen[key].quantity}</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
prettyName(name) {
|
||||
//make the enemy name a link
|
||||
if (name === this.props.username) {
|
||||
return name;
|
||||
} else if (name) {
|
||||
return (<Link to={`/profile?username=${name}`}>{name}</Link>);
|
||||
} else {
|
||||
return (<span style={{color:'red'}}>???</span>);
|
||||
}
|
||||
}
|
||||
|
||||
parseDate(eventTime) {
|
||||
let month = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||
|
||||
let date = new Date(eventTime);
|
||||
return `${date.getDate()} ${month[date.getMonth()]}`;
|
||||
}
|
||||
|
||||
capitalizeFirstLetter(str) {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
};
|
||||
|
||||
SpyingLogRecord.propTypes = {
|
||||
username: PropTypes.string.isRequired,
|
||||
eventTime: PropTypes.string.isRequired,
|
||||
attacker: PropTypes.string,
|
||||
defender: PropTypes.string.isRequired,
|
||||
attackingUnits: PropTypes.number,
|
||||
success: PropTypes.string.isRequired,
|
||||
spoilsGold: PropTypes.number.isRequired,
|
||||
equipmentStolen: PropTypes.array.isRequired
|
||||
};
|
||||
|
||||
export default withRouter(SpyingLogRecord);
|
||||
Reference in New Issue
Block a user