"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",
"@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",
"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",
"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",
"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",
"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",
"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",
"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",
"@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",
th, td {
padding: 0.5rem;
}
+l
p, form, ul, ol {
line-height: 1.3rem;
margin: 0 2rem 2rem 2rem;
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;
text-align: center;
font-weight: bold;
min-width: 150px;
+ display: inline-block;
+}
+a.btn::before {
+ content: '';
+ clear: both;
}
button::after, .btn::after {
content: '\27EB';
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);
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);
.text-center {
text-align: center;
}
+.progress-bar {
+ border: solid 1px #fff;
+ text-align: center;
+ color: #fff;
+ width: 100%;
+ min-width: 100px;
+}
/** CUSTOMIZATIONS **/
form > div {
margin-bottom: 0;
}
-.progress-bar {
- border: solid 1px #fff;
- text-align: center;
- color: #fff;
- width: 100%;
-}
.unread td{
background-color: #373737;
}
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';
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);
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<City> = {
+ 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<void, string>('/server-stats', async req => {
const date = new Date();
return `
NO_CITY: 2000,
INSUFFICIENT_RESOURCE: 3000,
INVALID_BUILDING: 4000,
+ INVALID_BUILD_QUEUE: 4100,
INVALID_AMOUNT: 6000,
INVALID_UNIT: 5000,
DUPLICATE_CACHE_KEY: 900,
<td>${quickFindBuilding[queue.building_type].display}</td>
<td>${queue.amount}</td>
<td>${progressBar(now, duration)}</td>
+ <td width="50">
+ <a href="#" hx-post="/construction/${queue.id}/cancel" hx-trigger="click" class="danger-text close" title="Cancel Construction">×</a>
+ </td>
</tr>
`;
}
return sample.sector_id;
}
- async save(city: City) {
+ async save(city: Partial<City>) {
+ 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',