UI update for map with sector selector
authorxangelo <git@xangelo.ca>
Fri, 13 May 2022 04:44:33 +0000 (00:44 -0400)
committerxangelo <git@xangelo.ca>
Mon, 16 May 2022 18:40:49 +0000 (14:40 -0400)
Capitol Ships are now automatically placed into "sectors" (hard-coded for now)
that allow smaller map sizes, while still keeping density.

This included a large visual update to the map overview that includes
images for the different sectors, a proper overlaid grid, and per city
icons.

21 files changed:
dump.sql
package.json
public/assets/bg/mapoverview_1.png [new file with mode: 0644]
public/assets/bg/mapoverview_2.png [new file with mode: 0644]
public/assets/colony-ships/01.png [new file with mode: 0644]
public/assets/colony-ships/rebels/1.png [new file with mode: 0644]
public/assets/colony-ships/rebels/2.png [new file with mode: 0644]
public/assets/colony-ships/rebels/3.png [new file with mode: 0644]
public/assets/colony-ships/rebels/4.png [new file with mode: 0644]
public/assets/colony-ships/rebels/5.png [new file with mode: 0644]
public/assets/colony-ships/rebels/6.png [new file with mode: 0644]
public/game.html
public/scifi.css
public/stylesheet.css
scripts/generate-cities.ts
src/api.ts
src/config.ts
src/render/fight.ts [deleted file]
src/render/kingdom-overview.ts
src/render/map.ts [new file with mode: 0644]
src/repository/city.ts

index 9cd3202a88e9b7ecccf2a96cc7d89306d0cb917d..9330aa6294a19cf23187011d69b74d33f12a65f7 100644 (file)
--- a/dump.sql
+++ b/dump.sql
@@ -8,6 +8,7 @@ CREATE TABLE IF NOT EXISTS "accounts" (
 CREATE TABLE IF NOT EXISTS "cities" (
        "id"    string,
        "owner" string,
+  "icon" string,
        "totalSpace"    int,
        "usedSpace"     int,
        "gold"  int,
@@ -118,4 +119,4 @@ INSERT INTO "units" VALUES ('soldiers','Soldiers',2,1,1,0,0,0,2,2.1,1);
 INSERT INTO "units" VALUES ('attackers','Attackers',5,2,0,1,0,0,3,4,1);
 INSERT INTO "units" VALUES ('defenders','Defenders',4,2,0,1,0,0,5,1,4);
 INSERT INTO "units" VALUES ('sp_attackers','Sp. Attacker',9,4,0,0,1,0,7,7,3);
-INSERT INTO "units" VALUES ('sp_defenders','Sp. Defender',11,5,0,0,0,1,10,2,9);
\ No newline at end of file
+INSERT INTO "units" VALUES ('sp_defenders','Sp. Defender',11,5,0,0,0,1,10,2,9);
index 25f5e4aa36f8004e14b7d0c21bb8b6d452ce0dd7..7deac18c0431a220a5a6461300443c6938ff026e 100644 (file)
@@ -5,7 +5,7 @@
                "dev": "npx nodemon src/api.ts",
                "setup:rebels": "npx ts-node scripts/generate-cities.ts",
                "setup:database": "npx ts-node scripts/setup.ts",
-               "setup": "npm run setup:database && npm run setup:rebels"
+               "setup": "npm run setup:rebels"
        },
        "dependencies": {
                "@bull-board/api": "^3.11.0",
diff --git a/public/assets/bg/mapoverview_1.png b/public/assets/bg/mapoverview_1.png
new file mode 100644 (file)
index 0000000..edc5ab2
Binary files /dev/null and b/public/assets/bg/mapoverview_1.png differ
diff --git a/public/assets/bg/mapoverview_2.png b/public/assets/bg/mapoverview_2.png
new file mode 100644 (file)
index 0000000..fcaedf7
Binary files /dev/null and b/public/assets/bg/mapoverview_2.png differ
diff --git a/public/assets/colony-ships/01.png b/public/assets/colony-ships/01.png
new file mode 100644 (file)
index 0000000..3a638a7
Binary files /dev/null and b/public/assets/colony-ships/01.png differ
diff --git a/public/assets/colony-ships/rebels/1.png b/public/assets/colony-ships/rebels/1.png
new file mode 100644 (file)
index 0000000..cfda262
Binary files /dev/null and b/public/assets/colony-ships/rebels/1.png differ
diff --git a/public/assets/colony-ships/rebels/2.png b/public/assets/colony-ships/rebels/2.png
new file mode 100644 (file)
index 0000000..98109df
Binary files /dev/null and b/public/assets/colony-ships/rebels/2.png differ
diff --git a/public/assets/colony-ships/rebels/3.png b/public/assets/colony-ships/rebels/3.png
new file mode 100644 (file)
index 0000000..52c7aec
Binary files /dev/null and b/public/assets/colony-ships/rebels/3.png differ
diff --git a/public/assets/colony-ships/rebels/4.png b/public/assets/colony-ships/rebels/4.png
new file mode 100644 (file)
index 0000000..ded5e31
Binary files /dev/null and b/public/assets/colony-ships/rebels/4.png differ
diff --git a/public/assets/colony-ships/rebels/5.png b/public/assets/colony-ships/rebels/5.png
new file mode 100644 (file)
index 0000000..eb5be25
Binary files /dev/null and b/public/assets/colony-ships/rebels/5.png differ
diff --git a/public/assets/colony-ships/rebels/6.png b/public/assets/colony-ships/rebels/6.png
new file mode 100644 (file)
index 0000000..3f26045
Binary files /dev/null and b/public/assets/colony-ships/rebels/6.png differ
index dedfa56a1d95041ceae22a85d29f90a7257fdd29..3596ad2d0d0e3ed25f7746b36359c3942de05a20 100644 (file)
@@ -34,7 +34,7 @@
                                 <a href="#" hx-target="#main" hx-get="/poll/unit-training" hx-trigger="click">Unit Training</a>
                             </li>
                             <li>
-                                <a href="#" hx-target="#main" hx-get="/poll/map" hx-trigger="click">Map</a>
+                                <a href="#" hx-target="#main" hx-post="/poll/map" hx-trigger="click">Map</a>
                             </li>
                             <li>
                                 <a href="#" hx-target="#main" hx-get="/poll/mailroom" hx-trigger="click">Mail</a>
@@ -64,4 +64,4 @@
         A project by <a href="https://xangelo.ca">xangelo</a>
     </footer>
 </body>
-</html>
\ No newline at end of file
+</html>
index 2aa04852608cf1c96f1bab8f2440f7856826cde4..c9e6da2846c542ae82b65f88737a01f10fb671b1 100644 (file)
@@ -4,6 +4,7 @@
     --page-bg: #061619;
     --green-bg: #193818;
     --green-border: #32821c;
+    --red-border: #821c1c;
 }
 
 input {
@@ -89,7 +90,7 @@ button::after, .btn::after {
 }
 .danger {
     background-color: #381818;
-    border-color: #821c1c;
+    border-color: var(--red-border);
 }
 .danger::after {
     color: #821c1c
@@ -249,4 +250,48 @@ form > div {
     padding: 10px 25px;
     background-color: var(--page-bg);
     justify-content: space-evenly;
-}
\ No newline at end of file
+}
+
+#overworld-map {
+  width: auto;
+  margin: 0 auto;
+}
+#overworld-map td {
+    overflow: hidden;
+    padding: 0;
+    background: transparent;
+    border: solid 1px #000;
+}
+#overworld-map td div {
+    width: 32px;
+    height: 32px;
+    overflow: hidden;
+}
+
+#overworld-map td div.city {
+    cursor: pointer;
+}
+#overworld-map td div.city.yours {
+  border: solid 1px var(--green-border);
+}
+#overworld-map td div.city.yours img {
+  width: 100%;
+}
+#overworld-map td div.city.others {
+  border: solid 1px var(--red-border);
+}
+#overworld-map td div.city.others img {
+  width: 100%;
+}
+
+#overworld-map .grid-numbers {
+  color: #7b7b7b;
+  text-align: center;
+}
+
+#sector-selector {
+  margin: 20px 50px;
+}
+#sector-selector select {
+  margin-left: 20px;
+}
index 4541f8e146ffbf10156998310eacaea568f99b1e..609d28ea7868673fa0e7d6c457509f4013e1dd37 100644 (file)
@@ -1,6 +1,3 @@
-#home form > div {
-    line-height: 3rem;
-}
 
 table form {
     margin-bottom: 0;
@@ -15,29 +12,6 @@ table form input {
     color: #fff;
     width: 100%;
 }
-
-#overworld-map td {
-    overflow: hidden;
-    padding: 0;
-}
-
-#overworld-map td div {
-    width: 5px;
-    height: 8px;
-    overflow: hidden;
-}
-
-#overworld-map td div.city {
-    cursor: pointer;
-}
-
-#overworld-map td div.city.yours {
-    background-color: #eee;
-}
-#overworld-map td div.city.others {
-    background-color: #a00;
-}
-
 .unread td{
     background-color: #373737;
-}
\ No newline at end of file
+}
index 5583b86745e74417f6eaa35db7b44602e2e8c377..43b2f390fbf882e6a770c1a3012bf87dc067b065 100644 (file)
@@ -5,10 +5,10 @@ const cityRepo = new CityRepository();
 
 (async () => {
     for(let i = 0; i < 100; ++i) {
-        await cityRepo.create(uuid());
+        await cityRepo.create(uuid(), true);
     }
 
     console.log('Created 100 cities');
 
     process.exit(0);
-})();
\ No newline at end of file
+})();
index 5f48de443d383b76ec6375fd80d9ef63f0088a08..aba677b1f72ad8c1e6f6dc59a2ca8471481441e5 100644 (file)
@@ -11,7 +11,7 @@ import { construction } from './tasks/construction';
 import { unitTraining } from './tasks/unit-training';
 import { fight } from './tasks/fight';
 import { renderUnitTraining } from './render/unit-training';
-import { launchOffensive, listOperations, renderOverworldMap } from './render/fight';
+import { launchOffensive, listOperations, renderOverworldMap } from './render/map';
 import { createBullBoard } from '@bull-board/api';
 import { BullAdapter } from '@bull-board/api/bullAdapter';
 import _ from 'lodash';
@@ -117,11 +117,23 @@ server.get<{}, string>('/poll/unit-training', async req => {
        return renderUnitTraining(city, units, unitTrainingQueues);
 });
 
-server.get<{}, string>('/poll/map', async req => {
+server.post<{body: {sector: string}}, string>('/poll/map', async req => {
        const account = await accountRepo.validate(req.authInfo.accountId, req.authInfo.token);
        const city = await cityRepo.getUsersCity(account.id);
 
-       return renderOverworldMap(await cityRepo.findAllInSector(city.sector_id), city);
+  let sector = city.sector_id;
+  if(req.body.sector) {
+    try {
+      sector = parseInt(req.body.sector);
+    }
+    catch(e) {
+      sector = city.sector_id;
+    }
+  }
+
+  console.log('Checking cities in sector', sector);
+
+       return renderOverworldMap(await cityRepo.findAllInSector(sector), city, sector);
 });
 
 server.get<{}, string>('/poll/mailroom', async req => {
@@ -285,4 +297,4 @@ server.get<void, string>('/attacks/outgoing', async req => {
 });
 
 
-server.start();
\ No newline at end of file
+server.start();
index ad7a3c5b976506898be2bf10b888898752b4295a..ba4d32e25aa97809d9c2071b54744b0d2d6def34 100644 (file)
@@ -4,4 +4,4 @@ dotenv();
 
 export const TICK_LENGTH = process.env.TICK_LENGTH || 1000 * 60 * 5;
 export const API_PORT = process.env.API_PORT || 9090;
-export const AVAILABLE_SECTORS = 1
+export const AVAILABLE_SECTORS = 2
diff --git a/src/render/fight.ts b/src/render/fight.ts
deleted file mode 100644 (file)
index ee3fbe4..0000000
+++ /dev/null
@@ -1,151 +0,0 @@
-import _ from "lodash";
-import { DateTime } from "luxon";
-import { Account } from "../repository/accounts";
-import { ArmyQueueWithAttackedOwner } from "../repository/army";
-import { City, CityRepository, CityWithLocation } from "../repository/city";
-import { topbar } from "./topbar";
-
-const cityRepo = new CityRepository();
-
-export function renderOverworldMap(cities: CityWithLocation[], yourCity: CityWithLocation): string {
-    let map: CityWithLocation[][] = new Array(25);
-    for(let i = 0; i < cities.length; ++i) {
-        if(!map[cities[i].location_y]) {
-            map[cities[i].location_y] = new Array(25);
-        }
-
-        map[cities[i].location_y][cities[i].location_x] = cities[i];
-    }
-
-    let rows: string[] = [];
-    for(let y = 0; y < 25; ++y) {
-        rows[y] = '<tr>';
-        for(let x = 0; x < 25; ++x) {
-            if(!map[y] || !map[y][x]) {
-                rows[y] += '<td><div class="empty"></div></td>' ;
-            }
-            else if(map[y][x]) {
-                if(map[y][x].id === yourCity.id)
-                    rows[y] += '<td><div class="city yours"></div></td>';
-                else
-                    rows[y] += `<td><div class="city others" hx-trigger="click" hx-get="/city/${map[y][x].id}" hx-target="#city-offence"></div></td>`;
-            }
-            else {
-                rows[y] += '<td><div class="empty"></div></td>';
-            }
-        }
-        rows[y] += '</tr>';
-    }
-    let html = `
-    <h2 data-augmented-ui="tl-clip bl-clip none">Map</h2>
-    <div id="city-offence"></div>
-    <div id="outgoing-attacks" hx-trigger="reload-outgoing-attacks, every 600s, load" hx-get="/attacks/outgoing"></div>
-    <table id="overworld-map">
-    ${rows.join("\n")}
-    </table>
-    
-    `;
-
-    return html;
-
-}
-
-export function listOperations(outgoingOps: ArmyQueueWithAttackedOwner[]): string {
-    let html = `
-    <h4>Outgoing Attacks</h4>
-    <table>
-    <tr>
-        <th>Destination</th>
-        <th>Sol.</th>
-        <th>Atk.</th>
-        <th>Def.</th>
-        <th>Sp. Atk.</th>
-        <th>Sp. Def.</th>
-        <th>Est. Arrival</th>
-    </tr>
-
-    ${outgoingOps.map(op => {
-        return `
-        <tr>
-            <td>${op.attacked_account} - (${op.location_x},${op.location_y})</td>
-            <td>${op.soldiers.toLocaleString()}</td>
-            <td>${op.attackers.toLocaleString()}</td>
-            <td>${op.defenders.toLocaleString()}</td>
-            <td>${op.sp_attackers.toLocaleString()}</td>
-            <td>${op.sp_defenders.toLocaleString()}</td>
-            <td>${ _.round(DateTime.fromMillis(op.due).diffNow().as('hours'), 2)} hours</td>
-        </tr>
-        `;
-    }).join("\n")}
-    </table>
-    `;
-
-    return html;
-}
-
-export async function launchOffensive(city: CityWithLocation, owner: Account, yourCity: CityWithLocation, you: Account): Promise<string> {
-    const distance = cityRepo.distanceInHours(city, yourCity);
-    const power = await cityRepo.power({
-        soldiers: yourCity.soldiers,
-        attackers: yourCity.attackers,
-        defenders: yourCity.defenders,
-        sp_attackers: yourCity.sp_attackers,
-        sp_defenders: yourCity.sp_defenders
-    })
-    let html = `
-    <h3 data-augmented-ui="tl-clip bl-clip none">Viewing (${city.location_x},${city.location_y}) owned by ${owner.username}</h3>
-    <h4>Report</h4>
-    <p>This city is ${distance} hours away.</p>
-    <form hx-post="/attack">
-    <input type="hidden" name="city" value="${city.id}">
-    <table>
-    <tr>
-        <th>Soldiers</th>
-        <td>
-        <input type="number" class="potential-attack" name="soldiers" max="${yourCity.soldiers}" value="${yourCity.soldiers}" hx-target="#total-attack-power" hx-post="/attack-power" hx-include=".potential-attack">
-        (${yourCity.soldiers})
-        </td>
-    </tr>
-    <tr>
-        <th>Attackers</th>
-        <td>
-        <input type="number" class="potential-attack" name="attackers" max="${yourCity.attackers}" value="${yourCity.attackers}" hx-target="#total-attack-power" hx-post="/attack-power" hx-include=".potential-attack">
-        (${yourCity.attackers})
-        </td>
-    </tr>
-    <tr>
-        <th>Defenders</th>
-        <td>
-        <input type="number" class="potential-attack" name="defenders" max="${yourCity.defenders}" value="${yourCity.defenders}" hx-target="#total-attack-power" hx-post="/attack-power" hx-include=".potential-attack">
-        (${yourCity.defenders})
-        </td>
-    </tr>
-    <tr>
-        <th>Sp. Attack</th>
-        <td>
-        <input type="number" class="potential-attack" name="sp_attackers" max="${yourCity.sp_attackers}" value="${yourCity.sp_attackers}" hx-target="#total-attack-power" hx-post="/attack-power" hx-include=".potential-attack">
-        (${yourCity.sp_attackers})
-        </td>
-    </tr>
-    <tr>
-        <th>Sp. Defenders</th>
-        <td>
-        <input type="number" class="potential-attack" name="sp_defenders" max="${yourCity.sp_defenders}" value="${yourCity.sp_defenders}" hx-target="#total-attack-power" hx-post="/attack-power" hx-include=".potential-attack">
-        (${yourCity.sp_defenders})
-        </td>
-    </tr>
-    <tr>
-        <th>Total Power</th>
-        <td id="total-attack-power">${power}</td>
-    </tr>
-    <tr>
-        <td colspan="2" style="text-align: right">
-            <button type="submit">Attack (${city.location_x},${city.location_y})</button>
-        </td>
-    </tr>
-    </table>
-    </form>
-    `;
-
-    return html + topbar(yourCity);
-}
\ No newline at end of file
index 9fd07ab18be444963af57fd5c82e0a4c69fa493e..c6b5964c1d615ca4a57844b8ba3e2aafa2f21ae6 100644 (file)
@@ -22,7 +22,7 @@ export function renderKingomOverview(city: CityWithLocation, account: Account):
        </tr>
        <tr>
                <th>Location</th>
-               <td>${city.location_x},${city.location_y}</td>
+               <td>${city.sector_id} - (${city.location_x},${city.location_y})</td>
                <th>Attackers</th>
                <td>${city.attackers.toLocaleString()}</td>
        </tr>
@@ -51,4 +51,4 @@ export function renderKingomOverview(city: CityWithLocation, account: Account):
        </table>
        </div>
        ${topbar(city)}`;
-}
\ No newline at end of file
+}
diff --git a/src/render/map.ts b/src/render/map.ts
new file mode 100644 (file)
index 0000000..219103d
--- /dev/null
@@ -0,0 +1,185 @@
+import _ from "lodash";
+import { DateTime } from "luxon";
+import { Account } from "../repository/accounts";
+import { ArmyQueueWithAttackedOwner } from "../repository/army";
+import { City, CityRepository, CityWithLocation } from "../repository/city";
+import { topbar } from "./topbar";
+import { AVAILABLE_SECTORS } from '../config';
+import {initArray} from "../lib/util";
+
+const cityRepo = new CityRepository();
+
+export function renderOverworldMap(cities: CityWithLocation[], yourCity: CityWithLocation, activeSector: number): string {
+    let map: CityWithLocation[][] = new Array(25);
+    for(let i = 0; i < cities.length; ++i) {
+        if(!map[cities[i].location_y]) {
+            map[cities[i].location_y] = new Array(25);
+        }
+
+        map[cities[i].location_y][cities[i].location_x] = cities[i];
+    }
+
+    let rows: string[] = [];
+    let headerRow: string[] = [];
+    for(let y = 0; y <= 25; ++y) {
+        rows[y] = '<tr>';
+        if(y === 0) {
+          // first row! add the X coors
+          for(let x = 0; x <= 25; ++x) {
+            headerRow.push(`<td class="grid-numbers">${x}</td>`);
+          }
+        }
+        else {
+          for(let x = 0; x <= 25; ++x) {
+            if(x === 0) {
+              rows[y] += `<td class="grid-numbers">${y}</td>`;
+            }
+            else {
+              if(!map[y] || !map[y][x]) {
+                  rows[y] += `<td title="(${x},${y})"><div class="empty"></div></td>` ;
+              }
+              else if(map[y][x]) {
+                  if(map[y][x].id === yourCity.id)
+                      rows[y] += `<td title="You (${x},${y})"><div class="city yours"><img src="/assets/${map[y][x].icon}"></div></td>`;
+                  else
+                      rows[y] += `<td title="(${x},${y})"><div class="city others" hx-trigger="click" hx-get="/city/${map[y][x].id}" hx-target="#city-offence">
+                    <img src="/assets/${map[y][x].icon}">
+                    </div></td>`;
+              }
+              else {
+                  rows[y] += '<td><div class="empty"></div></td>';
+              }
+            }
+          }
+        }
+        rows[y] += '</tr>';
+    }
+    let sectorSwitch: string[] = [];
+    for(let i = 1; i <= AVAILABLE_SECTORS; ++i) {
+      sectorSwitch.push(
+        `<option value="${i}" ${activeSector === i ? 'selected': ''}>Sector ${i}</option>`
+      );
+    }
+    let html = `
+    <h2 data-augmented-ui="tl-clip bl-clip none">Map</h2>
+    <div id="city-offence"></div>
+    <div id="outgoing-attacks" hx-trigger="reload-outgoing-attacks, every 600s, load" hx-get="/attacks/outgoing"></div>
+    <div id="sector-selector" class="text-right">
+      Switch Scanned Sectors: 
+      <select name="sector" hx-trigger="change" hx-post="/poll/map" hx-target="#main">
+        ${sectorSwitch.join("\n")}
+      </select>
+    </div>
+    <table id="overworld-map" style="background-image:url('/assets/bg/mapoverview_${activeSector}.png');">
+    <tr>
+    ${headerRow.join("\n")}
+    </tr>
+    ${rows.join("\n")}
+    </table>
+    <br>
+    `;
+
+
+    return html;
+
+}
+
+export function listOperations(outgoingOps: ArmyQueueWithAttackedOwner[]): string {
+    let html = `
+    <h4>Outgoing Attacks</h4>
+    <table>
+    <tr>
+        <th>Destination</th>
+        <th>Sol.</th>
+        <th>Atk.</th>
+        <th>Def.</th>
+        <th>Sp. Atk.</th>
+        <th>Sp. Def.</th>
+        <th>Est. Arrival</th>
+    </tr>
+
+    ${outgoingOps.map(op => {
+        return `
+        <tr>
+            <td>${op.attacked_account} - (${op.location_x},${op.location_y})</td>
+            <td>${op.soldiers.toLocaleString()}</td>
+            <td>${op.attackers.toLocaleString()}</td>
+            <td>${op.defenders.toLocaleString()}</td>
+            <td>${op.sp_attackers.toLocaleString()}</td>
+            <td>${op.sp_defenders.toLocaleString()}</td>
+            <td>${ _.round(DateTime.fromMillis(op.due).diffNow().as('hours'), 2)} hours</td>
+        </tr>
+        `;
+    }).join("\n")}
+    </table>
+    `;
+
+    return html;
+}
+
+export async function launchOffensive(city: CityWithLocation, owner: Account, yourCity: CityWithLocation, you: Account): Promise<string> {
+    const distance = cityRepo.distanceInHours(city, yourCity);
+    const power = await cityRepo.power({
+        soldiers: yourCity.soldiers,
+        attackers: yourCity.attackers,
+        defenders: yourCity.defenders,
+        sp_attackers: yourCity.sp_attackers,
+        sp_defenders: yourCity.sp_defenders
+    })
+    let html = `
+    <h3 data-augmented-ui="tl-clip bl-clip none">Viewing (${city.location_x},${city.location_y}) owned by ${owner.username}</h3>
+    <h4>Report</h4>
+    <p>This city is ${distance} hours away.</p>
+    <form hx-post="/attack">
+    <input type="hidden" name="city" value="${city.id}">
+    <table>
+    <tr>
+        <th>Soldiers</th>
+        <td>
+        <input type="number" class="potential-attack" name="soldiers" max="${yourCity.soldiers}" value="${yourCity.soldiers}" hx-target="#total-attack-power" hx-post="/attack-power" hx-include=".potential-attack">
+        (${yourCity.soldiers})
+        </td>
+    </tr>
+    <tr>
+        <th>Attackers</th>
+        <td>
+        <input type="number" class="potential-attack" name="attackers" max="${yourCity.attackers}" value="${yourCity.attackers}" hx-target="#total-attack-power" hx-post="/attack-power" hx-include=".potential-attack">
+        (${yourCity.attackers})
+        </td>
+    </tr>
+    <tr>
+        <th>Defenders</th>
+        <td>
+        <input type="number" class="potential-attack" name="defenders" max="${yourCity.defenders}" value="${yourCity.defenders}" hx-target="#total-attack-power" hx-post="/attack-power" hx-include=".potential-attack">
+        (${yourCity.defenders})
+        </td>
+    </tr>
+    <tr>
+        <th>Sp. Attack</th>
+        <td>
+        <input type="number" class="potential-attack" name="sp_attackers" max="${yourCity.sp_attackers}" value="${yourCity.sp_attackers}" hx-target="#total-attack-power" hx-post="/attack-power" hx-include=".potential-attack">
+        (${yourCity.sp_attackers})
+        </td>
+    </tr>
+    <tr>
+        <th>Sp. Defenders</th>
+        <td>
+        <input type="number" class="potential-attack" name="sp_defenders" max="${yourCity.sp_defenders}" value="${yourCity.sp_defenders}" hx-target="#total-attack-power" hx-post="/attack-power" hx-include=".potential-attack">
+        (${yourCity.sp_defenders})
+        </td>
+    </tr>
+    <tr>
+        <th>Total Power</th>
+        <td id="total-attack-power">${power}</td>
+    </tr>
+    <tr>
+        <td colspan="2" style="text-align: right">
+            <button type="submit">Attack (${city.location_x},${city.location_y})</button>
+        </td>
+    </tr>
+    </table>
+    </form>
+    `;
+
+    return html + topbar(yourCity);
+}
index df808786e71fbf7890b91b0ce35d573530d357b5..7b8818510e9b45761473d6da7411c27735ce4299 100644 (file)
@@ -30,6 +30,7 @@ export type City = {
     barracks: number;
     special_attacker_trainer: number;
     special_defender_trainer: number;
+    icon: string;
 }
 
 export type CityWithLocation = {
@@ -54,7 +55,8 @@ export class CityRepository extends Repository<City> {
         this.armyRepository = new ArmyRepository();
     }
 
-    async create(accountId: string): Promise<CityWithLocation> {
+    async create(accountId: string, rebel: boolean = false): Promise<CityWithLocation> {
+      const icon = rebel ? `/colony-ships/rebels/${random(1, 6)}.png` : '/colony-ships/01.png';
         const info: City = {
             id: uuid(),
             owner: accountId,
@@ -74,6 +76,7 @@ export class CityRepository extends Repository<City> {
             barracks: 0,
             special_attacker_trainer: 0,
             special_defender_trainer: 0,
+            icon
         };
 
         await this.Insert(info);
@@ -204,7 +207,10 @@ where l.sector_id = ?`, [sector_id]);
             Math.pow((city2.location_y - city1.location_y), 2)
         );
 
-        return _.round(dist/4, 2);
+        // sectors always add 4 hours
+        const sector_dist = Math.abs(city1.sector_id - city2.sector_id) * 6;
+
+        return _.round(dist/4, 2) + sector_dist;
 
     }