ability to cancel construction and have a portion of the funds returned
authorxangelo <git@xangelo.ca>
Mon, 6 Jun 2022 17:40:49 +0000 (13:40 -0400)
committerxangelo <git@xangelo.ca>
Mon, 6 Jun 2022 17:40:49 +0000 (13:40 -0400)
package-lock.json
package.json
public/scifi.css
public/stylesheet.css
src/api.ts
src/errors.ts
src/render/land-development.ts
src/repository/city.ts

index 1ebd7be36c34a7cccd9fbed3a0e26f76b3eec9cb..f3e411be41bf2aa2f507c2008afd29c8c45841ba 100644 (file)
@@ -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",
                        "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",
index 98bdd7f39ec12d02f7de23fdbe41fd568550bcf6..4b3db390a35bad54361d4b908ac520fdba3fa966 100644 (file)
@@ -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",
index 450fddf644601f3d5457272ac2bc7a944416bb92..98c99bd372922779a6c911ff779b07810f4f7b61 100644 (file)
@@ -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 {
index 609d28ea7868673fa0e7d6c457509f4013e1dd37..8d720a3e2eabb7b2f499a29d256c481578dc5bdc 100644 (file)
@@ -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;
 }
index 50d4f06d014eeb92b75c7bc8df2a63b665d4795b..4509bec431ad8d616bc0946d2fa2c0933268b2da 100644 (file)
@@ -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<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 `
index e245d9bb07f9c3437057764b8e0a196f6977c5a6..217008cd68ab73aeada74e3b3c6e670c81852b37 100644 (file)
@@ -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,
index e3643a9fc8732bc28d0b1cafcb15217ba43b07b3..d0b5e7f1157460eba318713ce9430a6475e93fb1 100644 (file)
@@ -95,6 +95,9 @@ export function renderLandDevelopment(city: CityWithLocation, buildings: Buildin
         <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">&times;</a>
+        </td>
         </tr>
         `;
       }
index a17d1d9696a0510ba62f1b6023c383a988e33b9a..c9ecb91b0aa358d96c9de6d4487087b328cd2670 100644 (file)
@@ -134,7 +134,10 @@ export class CityRepository extends Repository<City> {
         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',