Attacks are now pending

This commit is contained in:
2019-05-29 04:35:53 +10:00
parent f700daeb25
commit 2aeb9e7e10
8 changed files with 180 additions and 14 deletions
+3 -1
View File
@@ -13,7 +13,9 @@ let excluded = [ //messages that should not be logged
'Not enough time has passed', 'Not enough time has passed',
'Profile sent', 'Profile sent',
'Ladder sent' 'Ladder sent',
// 'attacking',
// 'idle'
]; ];
const log = (msg, ...args) => { const log = (msg, ...args) => {
+7 -1
View File
@@ -14,7 +14,13 @@ The plan is this:
* The chance of success is determined by the ratio of each side's combatant strength. * The chance of success is determined by the ratio of each side's combatant strength.
* Recruits have a strength equal to 0.25 times that of a soldier. * Recruits have a strength equal to 0.25 times that of a soldier.
* On a success, you steal 10% of the target's gold. On a failure, you steal 2% of the target's gold. * On a success, you steal 10% of the target's gold. On a failure, you steal 2% of the target's gold.
* The attacking force will lose a percentage, rounded down, of their units - 5% on a success, 10% on a failure. * The attacking force will lose a percentage, rounded down, of their units - 5% on a success, 10% on a failure (edit: excluding the first 10 units).
All of these numbers can be adjusted later, but this is the initial gameplan for combat. All of these numbers can be adjusted later, but this is the initial gameplan for combat.
Edit: More aspects that I'd like to ensure are:
* If the server resets (which happens alot) combat still progresses as expected.
* All combat is logged and presented to the player.
* You can only attack one person at a time.
+93 -4
View File
@@ -5,10 +5,99 @@ require('dotenv').config();
let { log } = require('../common/utilities.js'); let { log } = require('../common/utilities.js');
const attackRequest = (connection) => (req, res) => { const attackRequest = (connection) => (req, res) => {
res.status(400).write(log('Not yet implemented')); //verify the attacker's credentials
res.end(); let query = 'SELECT accountId FROM sessions WHERE accountId IN (SELECT id FROM accounts WHERE username = ?) AND token = ?;';
connection.query(query, [req.body.attacker, req.body.token], (err, results) => {
if (err) throw err;
if (results.length !== 1) {
res.status(400).write(log('Invalid attack credentials', req.body.attacker, req.body.defender, req.body.token));
res.end();
return;
}
let attackerId = results[0].accountId;
//verify that the defender exists
let query = 'SELECT id FROM accounts WHERE username = ?;';
connection.query(query, [req.body.defender], (err, results) => {
if (err) throw err;
if (results.length !== 1) {
res.status(400).write(log('Invalid defender credentials', req.body.attacker, req.body.defender));
res.end();
return;
}
let defenderId = results[0].id;
//verify that the attacker has enough soldiers
let query = 'SELECT soldiers FROM profiles WHERE accountId = ?;';
connection.query(query, [attackerId], (err, results) => {
if (err) throw err;
if (results[0].soldiers <= 0) {
res.status(400).write(log('Not enough soldiers', req.body.attacker, req.body.defender, results[0].soldiers));
res.end();
return;
}
let attackingUnits = results[0].soldiers;
//verify that the attacker is not already attacking someone
isAttacking(connection, req.body.attacker, (isAttacking) => {
if (isAttacking) {
res.status(400).write(log('You are already attacking someone', req.body.attacker, req.body.defender));
res.end();
return;
}
//create the pending attack value
let query = 'INSERT INTO pendingCombat (eventTime, attackerId, defenderId, attackingUnits) VALUES (DATE_ADD(CURRENT_TIMESTAMP(), INTERVAL 10 * ? SECOND), ?, ?, ?);';
connection.query(query, [attackingUnits, attackerId, defenderId, attackingUnits], (err) => {
if (err) throw err;
res.status(200).write(log(`Your soldiers are on their way to attack ${req.body.defender}`, req.body.attacker, req.body.defender));
res.end();
});
});
});
});
});
}
const attackStatusRequest = (connection) => (req, res) => {
isAttacking(connection, req.body.username, (isAttacking) => {
res.status(200).write(log(isAttacking ? 'attacking' : 'idle', req.body.username));
res.end();
});
}
const isAttacking = (connection, username, cb) => {
let query = 'SELECT * FROM pendingCombat WHERE attackerId IN (SELECT id FROM accounts WHERE username = ?);';
connection.query(query, [username], (err, results) => {
if (err) throw err;
return cb(results.length !== 0);
});
} }
module.exports = { module.exports = {
attackRequest: attackRequest attackRequest: attackRequest,
} attackStatusRequest: attackStatusRequest
}
/*
> You can attack another player using your soldiers (it doesn't work without soldiers).
* Doing so takes time, up to 10 seconds for every soldier you have.
* Combat takes place at the end of the time delay, at which point you can attack people again (after reloading the page).
> While attacking, you are undefended.
* While undefended, your recruits act as combatants, otherwise your soldiers do.
* The chance of success is determined by the ratio of each side's combatant strength.
* Recruits have a strength equal to 0.25 times that of a soldier.
* On a success, you steal 10% of the target's gold. On a failure, you steal 2% of the target's gold.
* The attacking force will lose a percentage, rounded down, of their units - 5% on a success, 10% on a failure (edit: excluding the first 10 units).
* If the server resets (which happens alot) combat still progresses as expected.
* All combat is logged and presented to the player.
*/
+1
View File
@@ -42,6 +42,7 @@ profiles.runGoldTick(connection);
let combat = require('./combat.js'); let combat = require('./combat.js');
app.post('/attackrequest', combat.attackRequest(connection)); app.post('/attackrequest', combat.attackRequest(connection));
app.post('/attackstatusrequest', combat.attackStatusRequest(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')) );
+38
View File
@@ -54,4 +54,42 @@ CREATE TABLE IF NOT EXISTS profiles (
lastRecruitTime TIMESTAMP DEFAULT '2019-01-01 00:00:00', lastRecruitTime TIMESTAMP DEFAULT '2019-01-01 00:00:00',
CONSTRAINT FOREIGN KEY fk_accountId(accountId) REFERENCES accounts(id) ON UPDATE CASCADE ON DELETE CASCADE CONSTRAINT FOREIGN KEY fk_accountId(accountId) REFERENCES accounts(id) ON UPDATE CASCADE ON DELETE CASCADE
);
#combat system
CREATE TABLE IF NOT EXISTS pendingCombat (
id INTEGER UNSIGNED AUTO_INCREMENT PRIMARY KEY UNIQUE,
td TIMESTAMP DEFAULT CURRENT_TIMESTAMP(),
eventTime TIMESTAMP,
attackerId INTEGER UNSIGNED,
defenderId INTEGER UNSIGNED,
attackingUnits INTEGER UNSIGNED,
CONSTRAINT FOREIGN KEY fk_attackerId(attackerId) REFERENCES accounts(id) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT FOREIGN KEY fk_defenderId(defenderId) REFERENCES accounts(id) ON UPDATE CASCADE ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS pastCombat (
id INTEGER UNSIGNED AUTO_INCREMENT PRIMARY KEY UNIQUE,
td TIMESTAMP DEFAULT CURRENT_TIMESTAMP(),
eventTime TIMESTAMP,
attackerId INTEGER UNSIGNED,
defenderId INTEGER UNSIGNED,
attackingUnits INTEGER UNSIGNED,
defindingUnits INTEGER UNSIGNED,
undefended BOOLEAN,
victor ENUM ('attacker', 'defender'),
spoilsGold INTEGER,
casualtiesVictor INTEGER,
CONSTRAINT FOREIGN KEY fk_attackerId(attackerId) REFERENCES accounts(id) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT FOREIGN KEY fk_defenderId(defenderId) REFERENCES accounts(id) ON UPDATE CASCADE ON DELETE CASCADE
); );
+1 -1
View File
@@ -218,7 +218,7 @@ class Profile extends React.Component {
<div className='row'> <div className='row'>
<p className='col'>Recruits:</p> <p className='col'>Recruits:</p>
<p className='col'>{this.state.recruits}</p> <p className='col'>{this.state.recruits}</p>
<AttackButton className='col' style={{flex: '2 1 1.5%'}} setWarning={this.setWarning.bind(this)} attacker={this.props.username} defender={this.state.username} /> <AttackButton className='col' style={{flex: '2 1 1.5%'}} setWarning={this.setWarning.bind(this)} attacker={this.props.username} defender={this.state.username} token={this.props.token} />
</div> </div>
<div className='row'> <div className='row'>
+36 -6
View File
@@ -8,14 +8,22 @@ class AttackButton extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
// message: ''
}; };
this.sendAttackingStatusRequest();
} }
render() { render() {
return ( if (this.state.message !== '') {
<button className={this.props.className} style={this.props.style} onClick={this.sendAttackRequest.bind(this)} disabled={this.props.disabled}>Attack</button> return (
); <p>{this.state.message}</p>
);
} else {
return (
<button className={this.props.className} style={this.props.style} onClick={this.sendAttackRequest.bind(this)} disabled={this.props.disabled}>Attack</button>
);
}
} }
sendAttackRequest() { sendAttackRequest() {
@@ -26,7 +34,7 @@ class AttackButton extends React.Component {
xhr.onreadystatechange = () => { xhr.onreadystatechange = () => {
if (xhr.readyState === 4) { if (xhr.readyState === 4) {
if (xhr.status === 200) { if (xhr.status === 200) {
//DO NOTHING this.setState({message: xhr.responseText});
} else if (xhr.status === 400) { } else if (xhr.status === 400) {
if (this.props.setWarning) { if (this.props.setWarning) {
this.props.setWarning(xhr.responseText); this.props.setWarning(xhr.responseText);
@@ -38,7 +46,8 @@ class AttackButton extends React.Component {
xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
xhr.send(JSON.stringify({ xhr.send(JSON.stringify({
attacker: this.props.attacker, attacker: this.props.attacker,
defender: this.props.defender defender: this.props.defender,
token: this.props.token
})); }));
if (this.props.onClick) { if (this.props.onClick) {
@@ -47,6 +56,26 @@ class AttackButton extends React.Component {
this.props.setDisabled(true); this.props.setDisabled(true);
} }
sendAttackingStatusRequest() {
let xhr = new XMLHttpRequest();
xhr.open('POST', '/attackstatusrequest', true);
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
if (xhr.responseText === 'attacking') {
this.props.setDisabled(true);
}
}
}
}
xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
xhr.send(JSON.stringify({
username: this.props.attacker
}));
}
}; };
AttackButton.propTypes = { AttackButton.propTypes = {
@@ -56,6 +85,7 @@ AttackButton.propTypes = {
setWarning: PropTypes.func, setWarning: PropTypes.func,
attacker: PropTypes.string.isRequired, attacker: PropTypes.string.isRequired,
defender: PropTypes.string.isRequired, defender: PropTypes.string.isRequired,
token: PropTypes.number.isRequired,
disabled: PropTypes.bool.isRequired, disabled: PropTypes.bool.isRequired,
setDisabled: PropTypes.func.isRequired setDisabled: PropTypes.func.isRequired
+1 -1
View File
@@ -1,7 +1,7 @@
import { SET_ATTACK_DISABLED } from '../actions/combat.js'; import { SET_ATTACK_DISABLED } from '../actions/combat.js';
const initialStore = { const initialStore = {
attackDisabled: true attackDisabled: false
}; };
export function combatReducer(store = initialStore, action) { export function combatReducer(store = initialStore, action) {