Implemented Capture The Flag
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
Priority List
|
||||
---
|
||||
|
||||
* Capture The Flag / King Of The Hill implemented.
|
||||
* ~~Capture The Flag / King Of The Hill implemented.~~
|
||||
* ~~Privacy policy / no children under 13.~~
|
||||
* ~~Unsubscribe from promotional emails.~~
|
||||
* Delete own account (right to be forgotten).
|
||||
@@ -76,7 +76,7 @@ Badge Ideas
|
||||
---
|
||||
|
||||
* ~~alpha tester~~
|
||||
* capture the flag
|
||||
* ~~capture the flag~~
|
||||
* ~~king of the hill~~
|
||||
* gold horde
|
||||
* ~~Combat Master~~
|
||||
|
||||
@@ -5,3 +5,8 @@ _16 June 2019_
|
||||
Things I've completed so far:
|
||||
|
||||
* 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",
|
||||
"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,
|
||||
"unlockable": null
|
||||
"unlockable": false
|
||||
},
|
||||
"Combat Master": {
|
||||
"filename": "combat_master.png",
|
||||
|
||||
@@ -70,6 +70,16 @@ const selectActiveBadge = (connection) => (req, res) => {
|
||||
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
|
||||
let query = 'UPDATE badges SET active = FALSE WHERE accountId = ?;';
|
||||
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) => {
|
||||
//Combat Master
|
||||
let combatMasterBadgeTickJob = new CronJob('0 * * * * *', () => { //once a minute - combats aren't that fast
|
||||
@@ -187,5 +224,6 @@ module.exports = {
|
||||
ownedRequest: ownedRequest,
|
||||
selectActiveBadge: selectActiveBadge,
|
||||
rewardBadge: rewardBadge,
|
||||
captureTheFlag: captureTheFlag,
|
||||
runBadgeTicks: runBadgeTicks
|
||||
};
|
||||
+28
-24
@@ -9,6 +9,7 @@ let { logDiagnostics } = require('./diagnostics.js');
|
||||
let { log } = require('../common/utilities.js');
|
||||
|
||||
let { getEquipmentStatistics, isAttacking, logActivity } = require('./utilities.js');
|
||||
let { captureTheFlag } = require('./badges.js');
|
||||
|
||||
const attackRequest = (connection) => (req, res) => {
|
||||
//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 attackerCasualties = Math.floor((pendingCombat.attackingUnits >= 10 ? pendingCombat.attackingUnits : 0) * (victor === 'attacker' ? Math.random() / 5 : Math.random() / 2));
|
||||
|
||||
//save the combat
|
||||
let query = 'INSERT INTO pastCombat (eventTime, attackerId, defenderId, attackingUnits, defendingUnits, undefended, victor, spoilsGold, attackerCasualties) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);';
|
||||
connection.query(query, [pendingCombat.eventTime, pendingCombat.attackerId, pendingCombat.defenderId, pendingCombat.attackingUnits, defendingUnits, undefended, victor, spoilsGold, attackerCasualties], (err) => {
|
||||
if (err) throw err;
|
||||
|
||||
//update the attacker profile
|
||||
let query = 'UPDATE profiles SET gold = gold + ?, soldiers = soldiers - ? WHERE accountId = ?;';
|
||||
connection.query(query, [spoilsGold, attackerCasualties, pendingCombat.attackerId], (err) => {
|
||||
//capture the flag logic
|
||||
captureTheFlag(connection, pendingCombat.attackerId, pendingCombat.defenderId, victor !== 'attacker', (flagCaptured) => {
|
||||
//save the combat
|
||||
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) => {
|
||||
if (err) throw err;
|
||||
|
||||
//update the defender profile
|
||||
let query = 'UPDATE profiles SET gold = gold - ? WHERE accountId = ?;';
|
||||
connection.query(query, [spoilsGold, pendingCombat.defenderId], (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;
|
||||
|
||||
//remove used consumables (moved because callback hell is rediculous)
|
||||
removeConsumables(connection, attackerConsumables, pendingCombat.attackingUnits);
|
||||
removeConsumables(connection, defenderConsumables, defendingUnits);
|
||||
|
||||
//delete the pending combat
|
||||
let query = 'DELETE FROM pendingCombat WHERE id = ?;';
|
||||
connection.query(query, [pendingCombat.id], (err) => {
|
||||
//update the defender profile
|
||||
let query = 'UPDATE profiles SET gold = gold - ? WHERE accountId = ?;';
|
||||
connection.query(query, [spoilsGold, pendingCombat.defenderId], (err) => {
|
||||
if (err) throw err;
|
||||
|
||||
log('Combat executed', pendingCombat.attackerId, pendingCombat.defenderId, victor, spoilsGold);
|
||||
logDiagnostics(connection, 'death', attackerCasualties);
|
||||
//remove used consumables (moved because callback hell is rediculous)
|
||||
removeConsumables(connection, attackerConsumables, pendingCombat.attackingUnits);
|
||||
removeConsumables(connection, defenderConsumables, defendingUnits);
|
||||
|
||||
//clean the database
|
||||
let query = 'DELETE FROM equipment WHERE quantity <= 0;';
|
||||
connection.query(query, (err) => {
|
||||
//delete the pending combat
|
||||
let query = 'DELETE FROM pendingCombat WHERE id = ?;';
|
||||
connection.query(query, [pendingCombat.id], (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 };
|
||||
});
|
||||
|
||||
//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 });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -118,6 +118,8 @@ CREATE TABLE IF NOT EXISTS pastCombat (
|
||||
|
||||
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_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
|
||||
accounts
|
||||
ADD COLUMN
|
||||
promotions BOOLEAN DEFAULT FALSE
|
||||
AFTER
|
||||
hash
|
||||
pastCombat
|
||||
ADD
|
||||
flagCaptured BOOLEAN NOT NULL DEFAULT FALSE
|
||||
;
|
||||
|
||||
#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;
|
||||
|
||||
//Capture The Flag forces your name to be yellow
|
||||
let colorOverride = {};
|
||||
if (this.props.name === 'Capture The Flag') {
|
||||
colorOverride.color = 'yellow';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={this.props.className} style={{...style, paddingBottom: '0.5em'}}>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ import React from 'react';
|
||||
import { withRouter, Link } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import BadgeText from './badge_text.jsx';
|
||||
|
||||
class CombatLogRecord extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@@ -12,6 +14,10 @@ class CombatLogRecord extends React.Component {
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className='panel table noCollapse'>
|
||||
<hr />
|
||||
@@ -23,7 +29,7 @@ class CombatLogRecord extends React.Component {
|
||||
</div>
|
||||
|
||||
<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'>Atk. Deaths: {this.props.attackerCasualties}</p>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user