Implemented Capture The Flag
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
Priority List
|
Priority List
|
||||||
---
|
---
|
||||||
|
|
||||||
* Capture The Flag / King Of The Hill implemented.
|
* ~~Capture The Flag / King Of The Hill implemented.~~
|
||||||
* ~~Privacy policy / no children under 13.~~
|
* ~~Privacy policy / no children under 13.~~
|
||||||
* ~~Unsubscribe from promotional emails.~~
|
* ~~Unsubscribe from promotional emails.~~
|
||||||
* Delete own account (right to be forgotten).
|
* Delete own account (right to be forgotten).
|
||||||
@@ -76,7 +76,7 @@ Badge Ideas
|
|||||||
---
|
---
|
||||||
|
|
||||||
* ~~alpha tester~~
|
* ~~alpha tester~~
|
||||||
* capture the flag
|
* ~~capture the flag~~
|
||||||
* ~~king of the hill~~
|
* ~~king of the hill~~
|
||||||
* gold horde
|
* gold horde
|
||||||
* ~~Combat Master~~
|
* ~~Combat Master~~
|
||||||
|
|||||||
@@ -5,3 +5,8 @@ _16 June 2019_
|
|||||||
Things I've completed so far:
|
Things I've completed so far:
|
||||||
|
|
||||||
* King Of The Hill badge is now enabled (requires 24 hours at the top of the ladder).
|
* King Of The Hill badge is now enabled (requires 24 hours at the top of the ladder).
|
||||||
|
* Capture the flag is implemented; user [Ratstail91](/profile?username=Ratstail91) will have it to begin with (that's me!).
|
||||||
|
|
||||||
|
Both of these badges are considered experimental, just for the record. I think I managed it, but don't be surprised if I have to roll the database back two days...
|
||||||
|
|
||||||
|
P.S. My code is so messy. So, so messy...
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
"filename": "capture_the_flag.png",
|
"filename": "capture_the_flag.png",
|
||||||
"description": "This badge is stolen by successful attacks. It is automatically set as your active badge, and you can't change it until it is lost; it also sets your name to yellow, so everyone knows you have it. Only one exists.",
|
"description": "This badge is stolen by successful attacks. It is automatically set as your active badge, and you can't change it until it is lost; it also sets your name to yellow, so everyone knows you have it. Only one exists.",
|
||||||
"visible": true,
|
"visible": true,
|
||||||
"unlockable": null
|
"unlockable": false
|
||||||
},
|
},
|
||||||
"Combat Master": {
|
"Combat Master": {
|
||||||
"filename": "combat_master.png",
|
"filename": "combat_master.png",
|
||||||
|
|||||||
@@ -70,6 +70,16 @@ const selectActiveBadge = (connection) => (req, res) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//if Capture The Flag is active, don't change the active badge; return badges owned
|
||||||
|
if (owned["Capture The Flag"]) {
|
||||||
|
getBadgesOwned(connection, req.body.id, (err, results) => {
|
||||||
|
if (err) throw err;
|
||||||
|
res.status(200).json(results);
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
//zero out the user's selection
|
//zero out the user's selection
|
||||||
let query = 'UPDATE badges SET active = FALSE WHERE accountId = ?;';
|
let query = 'UPDATE badges SET active = FALSE WHERE accountId = ?;';
|
||||||
connection.query(query, [req.body.id], (err) => {
|
connection.query(query, [req.body.id], (err) => {
|
||||||
@@ -108,6 +118,33 @@ const rewardBadge = (connection, id, badgeName, cb) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const captureTheFlag = (connection, attackerId, defenderId, skip, cb) => {
|
||||||
|
//if this is a no-op
|
||||||
|
if (skip) {
|
||||||
|
return cb(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
//check to see if the flag belongs to the defender
|
||||||
|
let query = 'SELECT * FROM badges WHERE accountId = ? AND name = "Capture The Flag" LIMIT 1;';
|
||||||
|
connection.query(query, [defenderId], (err, results) => {
|
||||||
|
if (err) throw err;
|
||||||
|
|
||||||
|
//does the defender have this badge? If not, return
|
||||||
|
if (results.length === 0) {
|
||||||
|
return cb(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
//move the badge between accounts
|
||||||
|
let query = 'INSERT INTO badges (id, accountId, name, active) VALUES (?, ?, "Capture The Flag", FALSE) ON DUPLICATE KEY UPDATE accountId = VALUES(accountId), active = FALSE;';
|
||||||
|
connection.query(query, [results[0].id, attackerId], (err) => {
|
||||||
|
if (err) throw err;
|
||||||
|
|
||||||
|
log('Badge moved', attackerId, defenderId);
|
||||||
|
cb(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const runBadgeTicks = (connection) => {
|
const runBadgeTicks = (connection) => {
|
||||||
//Combat Master
|
//Combat Master
|
||||||
let combatMasterBadgeTickJob = new CronJob('0 * * * * *', () => { //once a minute - combats aren't that fast
|
let combatMasterBadgeTickJob = new CronJob('0 * * * * *', () => { //once a minute - combats aren't that fast
|
||||||
@@ -187,5 +224,6 @@ module.exports = {
|
|||||||
ownedRequest: ownedRequest,
|
ownedRequest: ownedRequest,
|
||||||
selectActiveBadge: selectActiveBadge,
|
selectActiveBadge: selectActiveBadge,
|
||||||
rewardBadge: rewardBadge,
|
rewardBadge: rewardBadge,
|
||||||
|
captureTheFlag: captureTheFlag,
|
||||||
runBadgeTicks: runBadgeTicks
|
runBadgeTicks: runBadgeTicks
|
||||||
};
|
};
|
||||||
+28
-24
@@ -9,6 +9,7 @@ let { logDiagnostics } = require('./diagnostics.js');
|
|||||||
let { log } = require('../common/utilities.js');
|
let { log } = require('../common/utilities.js');
|
||||||
|
|
||||||
let { getEquipmentStatistics, isAttacking, logActivity } = require('./utilities.js');
|
let { getEquipmentStatistics, isAttacking, logActivity } = require('./utilities.js');
|
||||||
|
let { captureTheFlag } = require('./badges.js');
|
||||||
|
|
||||||
const attackRequest = (connection) => (req, res) => {
|
const attackRequest = (connection) => (req, res) => {
|
||||||
//verify the attacker's credentials (only the attacker can launch an attack)
|
//verify the attacker's credentials (only the attacker can launch an attack)
|
||||||
@@ -234,39 +235,42 @@ const runCombatTick = (connection) => {
|
|||||||
let spoilsGold = Math.floor(results[0].gold * (victor === 'attacker' ? 0.1 : 0.02));
|
let spoilsGold = Math.floor(results[0].gold * (victor === 'attacker' ? 0.1 : 0.02));
|
||||||
let attackerCasualties = Math.floor((pendingCombat.attackingUnits >= 10 ? pendingCombat.attackingUnits : 0) * (victor === 'attacker' ? Math.random() / 5 : Math.random() / 2));
|
let attackerCasualties = Math.floor((pendingCombat.attackingUnits >= 10 ? pendingCombat.attackingUnits : 0) * (victor === 'attacker' ? Math.random() / 5 : Math.random() / 2));
|
||||||
|
|
||||||
//save the combat
|
//capture the flag logic
|
||||||
let query = 'INSERT INTO pastCombat (eventTime, attackerId, defenderId, attackingUnits, defendingUnits, undefended, victor, spoilsGold, attackerCasualties) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);';
|
captureTheFlag(connection, pendingCombat.attackerId, pendingCombat.defenderId, victor !== 'attacker', (flagCaptured) => {
|
||||||
connection.query(query, [pendingCombat.eventTime, pendingCombat.attackerId, pendingCombat.defenderId, pendingCombat.attackingUnits, defendingUnits, undefended, victor, spoilsGold, attackerCasualties], (err) => {
|
//save the combat
|
||||||
if (err) throw err;
|
let query = 'INSERT INTO pastCombat (eventTime, attackerId, defenderId, attackingUnits, defendingUnits, undefended, victor, spoilsGold, attackerCasualties, flagCaptured) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);';
|
||||||
|
connection.query(query, [pendingCombat.eventTime, pendingCombat.attackerId, pendingCombat.defenderId, pendingCombat.attackingUnits, defendingUnits, undefended, victor, spoilsGold, attackerCasualties, flagCaptured], (err) => {
|
||||||
//update the attacker profile
|
|
||||||
let query = 'UPDATE profiles SET gold = gold + ?, soldiers = soldiers - ? WHERE accountId = ?;';
|
|
||||||
connection.query(query, [spoilsGold, attackerCasualties, pendingCombat.attackerId], (err) => {
|
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
|
|
||||||
//update the defender profile
|
//update the attacker profile
|
||||||
let query = 'UPDATE profiles SET gold = gold - ? WHERE accountId = ?;';
|
let query = 'UPDATE profiles SET gold = gold + ?, soldiers = soldiers - ? WHERE accountId = ?;';
|
||||||
connection.query(query, [spoilsGold, pendingCombat.defenderId], (err) => {
|
connection.query(query, [spoilsGold, attackerCasualties, pendingCombat.attackerId], (err) => {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
|
|
||||||
//remove used consumables (moved because callback hell is rediculous)
|
//update the defender profile
|
||||||
removeConsumables(connection, attackerConsumables, pendingCombat.attackingUnits);
|
let query = 'UPDATE profiles SET gold = gold - ? WHERE accountId = ?;';
|
||||||
removeConsumables(connection, defenderConsumables, defendingUnits);
|
connection.query(query, [spoilsGold, pendingCombat.defenderId], (err) => {
|
||||||
|
|
||||||
//delete the pending combat
|
|
||||||
let query = 'DELETE FROM pendingCombat WHERE id = ?;';
|
|
||||||
connection.query(query, [pendingCombat.id], (err) => {
|
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
|
|
||||||
log('Combat executed', pendingCombat.attackerId, pendingCombat.defenderId, victor, spoilsGold);
|
//remove used consumables (moved because callback hell is rediculous)
|
||||||
logDiagnostics(connection, 'death', attackerCasualties);
|
removeConsumables(connection, attackerConsumables, pendingCombat.attackingUnits);
|
||||||
|
removeConsumables(connection, defenderConsumables, defendingUnits);
|
||||||
|
|
||||||
//clean the database
|
//delete the pending combat
|
||||||
let query = 'DELETE FROM equipment WHERE quantity <= 0;';
|
let query = 'DELETE FROM pendingCombat WHERE id = ?;';
|
||||||
connection.query(query, (err) => {
|
connection.query(query, [pendingCombat.id], (err) => {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
|
|
||||||
log('Cleaned database', 'Combat consumables');
|
log('Combat executed', pendingCombat.attackerId, pendingCombat.defenderId, victor, spoilsGold);
|
||||||
|
logDiagnostics(connection, 'death', attackerCasualties);
|
||||||
|
|
||||||
|
//clean the database
|
||||||
|
let query = 'DELETE FROM equipment WHERE quantity <= 0;';
|
||||||
|
connection.query(query, (err) => {
|
||||||
|
if (err) throw err;
|
||||||
|
|
||||||
|
log('Cleaned database', 'Combat consumables');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -46,6 +46,12 @@ const getBadgesOwned = (connection, id, cb) => {
|
|||||||
ret[results[key].name] = { active: results[key].active };
|
ret[results[key].name] = { active: results[key].active };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//NOTE: check for "Capture The Flag" badge, force it to be the active badge
|
||||||
|
if ("Capture The Flag" in ret) {
|
||||||
|
Object.keys(ret).map((key) => ret[key].active = false);
|
||||||
|
ret["Capture The Flag"].active = true;
|
||||||
|
}
|
||||||
|
|
||||||
return cb(undefined, { 'owned': ret });
|
return cb(undefined, { 'owned': ret });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,6 +118,8 @@ CREATE TABLE IF NOT EXISTS pastCombat (
|
|||||||
|
|
||||||
attackerCasualties INTEGER,
|
attackerCasualties INTEGER,
|
||||||
|
|
||||||
|
flagCaptured BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
|
||||||
CONSTRAINT FOREIGN KEY fk_attackerId(attackerId) REFERENCES accounts(id) ON UPDATE CASCADE ON DELETE CASCADE,
|
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
|
CONSTRAINT FOREIGN KEY fk_defenderId(defenderId) REFERENCES accounts(id) ON UPDATE CASCADE ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|||||||
+15
-12
@@ -1,16 +1,19 @@
|
|||||||
ALTER TABLE
|
|
||||||
signups
|
|
||||||
ADD COLUMN
|
|
||||||
promotions BOOLEAN DEFAULT FALSE
|
|
||||||
AFTER
|
|
||||||
hash
|
|
||||||
;
|
|
||||||
|
|
||||||
ALTER TABLE
|
ALTER TABLE
|
||||||
accounts
|
pastCombat
|
||||||
ADD COLUMN
|
ADD
|
||||||
promotions BOOLEAN DEFAULT FALSE
|
flagCaptured BOOLEAN NOT NULL DEFAULT FALSE
|
||||||
AFTER
|
|
||||||
hash
|
|
||||||
;
|
;
|
||||||
|
|
||||||
|
#initialize the server's flag
|
||||||
|
INSERT INTO badges (accountId, name) VALUES (1, "Capture The Flag"); #my account ID
|
||||||
|
|
||||||
|
#move the badge between accounts
|
||||||
|
#INSERT INTO badges
|
||||||
|
# (id, accountId)
|
||||||
|
#VALUES
|
||||||
|
# (?, ?)
|
||||||
|
#ON DUPLICATE KEY UPDATE
|
||||||
|
# accountId = VALUES(accountId)
|
||||||
|
#;
|
||||||
|
|
||||||
|
|||||||
@@ -21,10 +21,16 @@ class BadgeText extends React.Component {
|
|||||||
|
|
||||||
let style = this.props.centered ? centerStyle : leftStyle;
|
let style = this.props.centered ? centerStyle : leftStyle;
|
||||||
|
|
||||||
|
//Capture The Flag forces your name to be yellow
|
||||||
|
let colorOverride = {};
|
||||||
|
if (this.props.name === 'Capture The Flag') {
|
||||||
|
colorOverride.color = 'yellow';
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={this.props.className} style={{...style, paddingBottom: '0.5em'}}>
|
<div className={this.props.className} style={{...style, paddingBottom: '0.5em'}}>
|
||||||
<Badge name={this.props.name} filename={this.props.filename} size={this.props.size} />
|
<Badge name={this.props.name} filename={this.props.filename} size={this.props.size} />
|
||||||
<p style={{paddingBottom: 0, ...this.props.style}}>{this.props.children}</p>
|
<p className='truncate' style={{paddingBottom: 0, ...this.props.style, ...colorOverride}}>{this.props.children}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import React from 'react';
|
|||||||
import { withRouter, Link } from 'react-router-dom';
|
import { withRouter, Link } from 'react-router-dom';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import BadgeText from './badge_text.jsx';
|
||||||
|
|
||||||
class CombatLogRecord extends React.Component {
|
class CombatLogRecord extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -12,6 +14,10 @@ class CombatLogRecord extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
//NOTE: the badgeText was never meant to be used this way
|
||||||
|
let badgeFilename = this.props.flagCaptured ? 'capture_the_flag.png' : undefined;
|
||||||
|
let badgeName = this.props.flagCaptured ? 'Capture The Flag' : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='panel table noCollapse'>
|
<div className='panel table noCollapse'>
|
||||||
<hr />
|
<hr />
|
||||||
@@ -23,7 +29,7 @@ class CombatLogRecord extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='row'>
|
<div className='row'>
|
||||||
<p className='col truncate'><span className='mobile hide'>Victor: </span>{this.capitalizeFirstLetter(this.props.victor)} {this.props.undefended ? '(undefended)' : ''}</p>
|
<BadgeText name={badgeName} filename={badgeFilename} size='small' className='col truncate'><span className='mobile hide'>Victor: </span>{this.capitalizeFirstLetter(this.props.victor)} {this.props.undefended ? '(undefended)' : ''}</BadgeText>
|
||||||
<p className='col truncate'>Gold: {this.props.spoilsGold}</p>
|
<p className='col truncate'>Gold: {this.props.spoilsGold}</p>
|
||||||
<p className='col truncate'>Atk. Deaths: {this.props.attackerCasualties}</p>
|
<p className='col truncate'>Atk. Deaths: {this.props.attackerCasualties}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user