From c85f9a55a180fd42e4a68f2f2a459889b7d67f30 Mon Sep 17 00:00:00 2001 From: Kayne Ruse Date: Sun, 16 Jun 2019 03:14:12 +1000 Subject: [PATCH] Implemented Capture The Flag --- public/content/task_list.md | 4 +- public/news/2019-06-16-01.md | 5 ++ server/badge_statistics.json | 2 +- server/badges.js | 38 +++++++++++++++ server/combat.js | 52 +++++++++++---------- server/utilities.js | 6 +++ sql/create_database_structure.sql | 2 + sql/update.sql | 27 ++++++----- src/components/panels/badge_text.jsx | 8 +++- src/components/panels/combat_log_record.jsx | 8 +++- 10 files changed, 111 insertions(+), 41 deletions(-) diff --git a/public/content/task_list.md b/public/content/task_list.md index cc4e01d..aabaf47 100644 --- a/public/content/task_list.md +++ b/public/content/task_list.md @@ -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~~ diff --git a/public/news/2019-06-16-01.md b/public/news/2019-06-16-01.md index 0d3499f..feb9460 100644 --- a/public/news/2019-06-16-01.md +++ b/public/news/2019-06-16-01.md @@ -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... \ No newline at end of file diff --git a/server/badge_statistics.json b/server/badge_statistics.json index b3271ab..6813a3a 100644 --- a/server/badge_statistics.json +++ b/server/badge_statistics.json @@ -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", diff --git a/server/badges.js b/server/badges.js index 121e39e..351617e 100644 --- a/server/badges.js +++ b/server/badges.js @@ -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 }; \ No newline at end of file diff --git a/server/combat.js b/server/combat.js index 4e03de7..3180462 100644 --- a/server/combat.js +++ b/server/combat.js @@ -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'); + }); }); }); }); diff --git a/server/utilities.js b/server/utilities.js index 300855b..aa6d52d 100644 --- a/server/utilities.js +++ b/server/utilities.js @@ -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 }); }); } diff --git a/sql/create_database_structure.sql b/sql/create_database_structure.sql index 5c3b03c..093ce99 100644 --- a/sql/create_database_structure.sql +++ b/sql/create_database_structure.sql @@ -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 ); diff --git a/sql/update.sql b/sql/update.sql index 69c1f65..7a564ab 100644 --- a/sql/update.sql +++ b/sql/update.sql @@ -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) +#; + diff --git a/src/components/panels/badge_text.jsx b/src/components/panels/badge_text.jsx index 199b61d..07bd904 100644 --- a/src/components/panels/badge_text.jsx +++ b/src/components/panels/badge_text.jsx @@ -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 (
-

{this.props.children}

+

{this.props.children}

); } diff --git a/src/components/panels/combat_log_record.jsx b/src/components/panels/combat_log_record.jsx index fb9f01c..2f70bcf 100644 --- a/src/components/panels/combat_log_record.jsx +++ b/src/components/panels/combat_log_record.jsx @@ -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 (

@@ -23,7 +29,7 @@ class CombatLogRecord extends React.Component {
-

Victor: {this.capitalizeFirstLetter(this.props.victor)} {this.props.undefended ? '(undefended)' : ''}

+ Victor: {this.capitalizeFirstLetter(this.props.victor)} {this.props.undefended ? '(undefended)' : ''}

Gold: {this.props.spoilsGold}

Atk. Deaths: {this.props.attackerCasualties}