From 9cb3a0584377f61cf3382fef4541246d1b1202bc Mon Sep 17 00:00:00 2001 From: xangelo Date: Mon, 6 Jun 2022 13:40:49 -0400 Subject: [PATCH] ability to cancel construction and have a portion of the funds returned --- package-lock.json | 29 ++++++++++++++++++++- package.json | 8 +++--- public/scifi.css | 41 ++++++++++++++++++++++-------- public/stylesheet.css | 6 ----- src/api.ts | 46 +++++++++++++++++++++++++++++++++- src/errors.ts | 1 + src/render/land-development.ts | 3 +++ src/repository/city.ts | 5 +++- 8 files changed, 116 insertions(+), 23 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1ebd7be..f3e411b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,8 @@ "luxon": "^1.28.0", "socket.io": "^4.5.1", "sqlite3": "^5.0.6", - "uuid": "^8.3.2" + "uuid": "^8.3.2", + "validator": "^13.7.0" }, "devDependencies": { "@types/bcrypt": "^5.0.0", @@ -29,6 +30,7 @@ "@types/lodash": "^4.14.182", "@types/luxon": "^2.3.2", "@types/uuid": "^8.3.4", + "@types/validator": "^13.7.2", "nodemon": "^2.0.16", "ts-node": "^10.7.0", "tsconfig-paths": "^4.0.0", @@ -622,6 +624,12 @@ "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==", "dev": true }, + "node_modules/@types/validator": { + "version": "13.7.2", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.2.tgz", + "integrity": "sha512-KFcchQ3h0OPQgFirBRPZr5F/sVjxZsOrQHedj3zi8AH3Zv/hOLx2OLR4hxR5HcfoU+33n69ZuOfzthKVdMoTiw==", + "dev": true + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -4631,6 +4639,14 @@ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "dev": true }, + "node_modules/validator": { + "version": "13.7.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz", + "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -5365,6 +5381,12 @@ "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==", "dev": true }, + "@types/validator": { + "version": "13.7.2", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.2.tgz", + "integrity": "sha512-KFcchQ3h0OPQgFirBRPZr5F/sVjxZsOrQHedj3zi8AH3Zv/hOLx2OLR4hxR5HcfoU+33n69ZuOfzthKVdMoTiw==", + "dev": true + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -8328,6 +8350,11 @@ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "dev": true }, + "validator": { + "version": "13.7.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz", + "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index 98bdd7f..4b3db39 100644 --- a/package.json +++ b/package.json @@ -3,10 +3,10 @@ "private": true, "scripts": { "dev": "npx nodemon src/api.ts", - "migrate": "npx ts-node --", + "migrate": "npx ts-node --", "setup:rebels": "npx ts-node scripts/generate-cities.ts", "setup": "npm run setup:rebels", - "migration": "npx ts-node migrations/" + "migration": "npx ts-node migrations/" }, "dependencies": { "@bull-board/api": "^3.11.0", @@ -23,7 +23,8 @@ "luxon": "^1.28.0", "socket.io": "^4.5.1", "sqlite3": "^5.0.6", - "uuid": "^8.3.2" + "uuid": "^8.3.2", + "validator": "^13.7.0" }, "devDependencies": { "@types/bcrypt": "^5.0.0", @@ -32,6 +33,7 @@ "@types/lodash": "^4.14.182", "@types/luxon": "^2.3.2", "@types/uuid": "^8.3.4", + "@types/validator": "^13.7.2", "nodemon": "^2.0.16", "ts-node": "^10.7.0", "tsconfig-paths": "^4.0.0", diff --git a/public/scifi.css b/public/scifi.css index 450fddf..98c99bd 100644 --- a/public/scifi.css +++ b/public/scifi.css @@ -62,6 +62,7 @@ tr:nth-child(odd) td, tr:nth-child(odd) th { th, td { padding: 0.5rem; } +l p, form, ul, ol { line-height: 1.3rem; margin: 0 2rem 2rem 2rem; @@ -73,6 +74,17 @@ label { margin-right: 5px; } +a { + font-weight: bold; + color: #fff; + text-decoration: none; +} +a::before { + content: '\27EA'; +} +a::after { + content: '\27EB'; +} button, .btn { border: solid 1px var(--border); background-color: #183238; @@ -84,6 +96,11 @@ button, .btn { text-align: center; font-weight: bold; min-width: 150px; + display: inline-block; +} +a.btn::before { + content: ''; + clear: both; } button::after, .btn::after { content: '\27EB'; @@ -116,6 +133,12 @@ button:active, .btn:active, button:hover, .btn:hover { button.success:active, .btn.success:active, button.success:hover, .btn.success:hover { background-color: #1e4c1a; } +button.danger:active, .btn.danger:active, button.danger:hover, .btn.danger:hover { + background-color: #601f1f; +} +a.close::before, a.close::after{ + content: ''; +} input[type="text"], input[type="password"], input[type="number"] { border: solid 1px var(--border); @@ -127,17 +150,6 @@ input[type="text"], input[type="password"], input[type="number"] { padding: 5px; } -a { - font-weight: bold; - color: #fff; - text-decoration: none; -} -a::before { - content: '\27EA'; -} -a::after { - content: '\27EB'; -} footer { text-align: center; border-top: solid 1px var(--border); @@ -235,6 +247,13 @@ footer { .text-center { text-align: center; } +.progress-bar { + border: solid 1px #fff; + text-align: center; + color: #fff; + width: 100%; + min-width: 100px; +} /** CUSTOMIZATIONS **/ form > div { diff --git a/public/stylesheet.css b/public/stylesheet.css index 609d28e..8d720a3 100644 --- a/public/stylesheet.css +++ b/public/stylesheet.css @@ -6,12 +6,6 @@ table form input { margin-bottom: 0; } -.progress-bar { - border: solid 1px #fff; - text-align: center; - color: #fff; - width: 100%; -} .unread td{ background-color: #373737; } diff --git a/src/api.ts b/src/api.ts index 50d4f06..4509bec 100644 --- a/src/api.ts +++ b/src/api.ts @@ -1,7 +1,7 @@ import { HttpServer } from './lib/server'; import * as config from './config'; import { AccountRepository } from './repository/accounts'; -import { CityRepository } from './repository/city'; +import { City, CityRepository } from './repository/city'; import { MailRepository } from './repository/mail'; import {BadInputError, ERROR_CODE, NotFoundError} from './errors'; import { renderKingomOverview } from './render/kingdom-overview'; @@ -19,6 +19,7 @@ import { renderCost } from './render/costs'; import {renderMailroom, renderMessage} from './render/mail'; import {topbar} from './render/topbar'; import {renderPublicChatMessage} from './render/chat-message'; +import validator from 'validator'; const server = new HttpServer(config.API_PORT); @@ -379,6 +380,49 @@ server.post<{body: {message: string}}, void>('/chat', async req => { return; }); +server.post<{params: {queueId: string}}, void>('/construction/:queueId/cancel', async req => { + const acct = await accountRepo.validate(req.authInfo.accountId, req.authInfo.token); + const city = await cityRepo.getUsersCity(acct.id); + + if(!validator.isUUID(req.params.queueId)) { + throw new BadInputError('Invalid queue ID', ERROR_CODE.INVALID_BUILD_QUEUE); + } + + // validate that this is an actual queue + const queue = await cityRepo.buildQueue.FindOne({owner: city.owner, id: req.params.queueId}); + + if(!queue) { + throw new NotFoundError('That queue does not exist', ERROR_CODE.INVALID_BUILD_QUEUE); + } + + const [, building] = await Promise.all([ + cityRepo.buildQueue.Delete({ + owner: city.owner, + id: req.params.queueId + }), + cityRepo.buildingRepository.findBySlug(queue.building_type) + ]); + + // now that it's deleted we can give the player back some percentage + // of resources based on how close they are to completion. + const diff = (queue.due - Date.now()) / (queue.due - queue.created); + // force a 20% loss minimum + const finalDiff = diff < 0.2 ? 0.2 : diff; + + const costReturn: Partial = { + id: city.id, + credits: city.credits + Math.floor(building.credits * queue.amount * diff), + alloys: city.alloys + Math.floor(building.alloys * queue.amount * diff), + energy: city.energy + Math.floor(building.energy * queue.amount * diff), + usedSpace: city.usedSpace - (building.land * queue.amount) + }; + + console.log('update', costReturn) + + await cityRepo.save(costReturn); + +}, 'reload-construction-queue'); + server.get('/server-stats', async req => { const date = new Date(); return ` diff --git a/src/errors.ts b/src/errors.ts index e245d9b..217008c 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -47,6 +47,7 @@ export const ERROR_CODE = { NO_CITY: 2000, INSUFFICIENT_RESOURCE: 3000, INVALID_BUILDING: 4000, + INVALID_BUILD_QUEUE: 4100, INVALID_AMOUNT: 6000, INVALID_UNIT: 5000, DUPLICATE_CACHE_KEY: 900, diff --git a/src/render/land-development.ts b/src/render/land-development.ts index e3643a9..d0b5e7f 100644 --- a/src/render/land-development.ts +++ b/src/render/land-development.ts @@ -95,6 +95,9 @@ export function renderLandDevelopment(city: CityWithLocation, buildings: Buildin ${quickFindBuilding[queue.building_type].display} ${queue.amount} ${progressBar(now, duration)} + + × + `; } diff --git a/src/repository/city.ts b/src/repository/city.ts index a17d1d9..c9ecb91 100644 --- a/src/repository/city.ts +++ b/src/repository/city.ts @@ -134,7 +134,10 @@ export class CityRepository extends Repository { return sample.sector_id; } - async save(city: City) { + async save(city: Partial) { + if(!city.id) { + throw new Error('Unknown city to save'); + } const fieldsToSave = [ 'totalSpace', 'usedSpace', 'credits', 'alloys', 'energy', 'food', 'poulation', 'soldiers', 'attackers', 'defenders', 'sp_attackers', 'sp_defenders', -- 2.25.1