Attacks are now pending
This commit is contained in:
+3
-1
@@ -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) => {
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
|||||||
+91
-2
@@ -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
|
||||||
|
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();
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -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')) );
|
||||||
|
|||||||
@@ -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
|
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
|
||||||
|
);
|
||||||
@@ -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'>
|
||||||
|
|||||||
@@ -8,15 +8,23 @@ class AttackButton extends React.Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
//
|
message: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.sendAttackingStatusRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
if (this.state.message !== '') {
|
||||||
|
return (
|
||||||
|
<p>{this.state.message}</p>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
return (
|
return (
|
||||||
<button className={this.props.className} style={this.props.style} onClick={this.sendAttackRequest.bind(this)} disabled={this.props.disabled}>Attack</button>
|
<button className={this.props.className} style={this.props.style} onClick={this.sendAttackRequest.bind(this)} disabled={this.props.disabled}>Attack</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sendAttackRequest() {
|
sendAttackRequest() {
|
||||||
//build the XHR
|
//build the XHR
|
||||||
@@ -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,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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user