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',
'Profile sent',
'Ladder sent'
'Ladder sent',
// 'attacking',
// 'idle'
];
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.
* 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.
* 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.
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.
+91 -2
View File
@@ -5,10 +5,99 @@ require('dotenv').config();
let { log } = require('../common/utilities.js');
const attackRequest = (connection) => (req, res) => {
res.status(400).write(log('Not yet implemented'));
//verify the attacker's credentials
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 = {
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');
app.post('/attackrequest', combat.attackRequest(connection));
app.post('/attackstatusrequest', combat.attackStatusRequest(connection));
//static directories
app.use('/styles', express.static(path.resolve(__dirname + '/../public/styles')) );
+38
View File
@@ -55,3 +55,41 @@ CREATE TABLE IF NOT EXISTS profiles (
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'>
<p className='col'>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 className='row'>
+33 -3
View File
@@ -8,15 +8,23 @@ class AttackButton extends React.Component {
constructor(props) {
super(props);
this.state = {
//
message: ''
};
this.sendAttackingStatusRequest();
}
render() {
if (this.state.message !== '') {
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() {
//build the XHR
@@ -26,7 +34,7 @@ class AttackButton extends React.Component {
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
//DO NOTHING
this.setState({message: xhr.responseText});
} else if (xhr.status === 400) {
if (this.props.setWarning) {
this.props.setWarning(xhr.responseText);
@@ -38,7 +46,8 @@ class AttackButton extends React.Component {
xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
xhr.send(JSON.stringify({
attacker: this.props.attacker,
defender: this.props.defender
defender: this.props.defender,
token: this.props.token
}));
if (this.props.onClick) {
@@ -47,6 +56,26 @@ class AttackButton extends React.Component {
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 = {
@@ -56,6 +85,7 @@ AttackButton.propTypes = {
setWarning: PropTypes.func,
attacker: PropTypes.string.isRequired,
defender: PropTypes.string.isRequired,
token: PropTypes.number.isRequired,
disabled: PropTypes.bool.isRequired,
setDisabled: PropTypes.func.isRequired
+1 -1
View File
@@ -1,7 +1,7 @@
import { SET_ATTACK_DISABLED } from '../actions/combat.js';
const initialStore = {
attackDisabled: true
attackDisabled: false
};
export function combatReducer(store = initialStore, action) {