WIP: update to include sectors!
[browser-rts.git] / src / repository / city.ts
index 4aaa12778842291b4746efc9da8f156b588e75a1..df808786e71fbf7890b91b0ce35d573530d357b5 100644 (file)
@@ -1,13 +1,14 @@
 import { v4 as uuid } from 'uuid';
 import { ERROR_CODE, InsufficientResourceError, NotFoundError } from '../errors';
 import {Repository} from './base';
+import * as config from '../config';
 import { BuildQueue, BuildQueueRepository } from './build-queue';
 import { DateTime, Duration } from 'luxon';
 import { UnitTrainingQueue, UnitTrainingQueueRepository } from './training-queue';
 import { coalesce } from '../lib/util';
 import { Building, BuildingRepository } from './buildings';
 import { Unit, UnitRepository } from './unit';
-import _ from 'lodash';
+import _, { random } from 'lodash';
 import { Army, ArmyQueue, ArmyRepository } from './army';
 
 export type City = {
@@ -29,9 +30,13 @@ export type City = {
     barracks: number;
     special_attacker_trainer: number;
     special_defender_trainer: number;
+}
+
+export type CityWithLocation = {
+    sector_id: number;
     location_x: number;
     location_y: number;
-}
+} & City;
 
 export class CityRepository extends Repository<City> {
     buildQueue: BuildQueueRepository;
@@ -49,7 +54,7 @@ export class CityRepository extends Repository<City> {
         this.armyRepository = new ArmyRepository();
     }
 
-    async create(accountId: string): Promise<City> {
+    async create(accountId: string): Promise<CityWithLocation> {
         const info: City = {
             id: uuid(),
             owner: accountId,
@@ -69,32 +74,81 @@ export class CityRepository extends Repository<City> {
             barracks: 0,
             special_attacker_trainer: 0,
             special_defender_trainer: 0,
-            location_x: _.random(0, 100),
-            location_y: _.random(0, 100)
         };
 
+        await this.Insert(info);
+
         // placement can happen randomly
+        const availableSectors = config.AVAILABLE_SECTORS;
+        const sector = _.random(1, availableSectors);
 
-        await this.Insert(info);
+        const location = {
+            sector_id: await this.getAvailableSector(),
+            location_x: random(0, 25),
+            location_y: random(0, 25)
+        }
+
+        await this.db.raw('insert into locations (sector_id, city_id, location_x, location_y) values (?, ?, ?, ?)', [
+            location.sector_id,
+            info.id,
+            location.location_x,
+            location.location_y
+        ]);
+
+        return {
+            ...info,
+            sector_id: location.sector_id,
+            location_x: location.location_x,
+            location_y: location.location_y
+        };
+    }
+
+    async getAvailableSector(): Promise<number> {
+        // figure out which sectors have space (40% fill rate at 25x25);
+        const availableSectors = await this.db.raw<{count: Number, sector_id: number}[]>(`select count(sector_id) as count, sector_id from locations group by sector_id`);
+        const sample = _.sample(availableSectors.filter(sector => sector.count < 250)) as {count: number, sector_id: number};
 
-        return info;
+        if(!sample) {
+            return _.sortBy(availableSectors, 'sector_id').pop().sector_id+1;
+        }
+        
+        return sample.sector_id;
     }
 
-    async save(city: City) {
+    async save(city: Partial<City>) {
         await this.Save(city, {id: city.id});
         return city;
     }
 
-    async getUsersCity(owner: string): Promise<City> {
-        const city = await this.FindOne({
-            owner
-        });
+    async findById(cityId: string): Promise<CityWithLocation> {
+        const city = await this.db.raw<CityWithLocation[]>(`select c.*, l.sector_id, l.location_x, l.location_y from cities c
+        join locations l on c.id = l.city_id 
+        where id = ? limit 1`, [cityId]);
 
         if(!city) {
             throw new NotFoundError('User has no city', ERROR_CODE.NO_CITY);
         }
 
-        return city;
+        return city.pop();
+
+    }
+
+    async getUsersCity(owner: string): Promise<CityWithLocation> {
+        const city = await this.db.raw<CityWithLocation[]>(`select c.*, l.sector_id, l.location_x, l.location_y from cities c
+        join locations l on c.id = l.city_id 
+        where owner = ? limit 1`, [owner]);
+
+        if(!city) {
+            throw new NotFoundError('User has no city', ERROR_CODE.NO_CITY);
+        }
+
+        return city.pop();
+    }
+
+    findAllInSector(sector_id: number): Promise<CityWithLocation[]> {
+        return this.db.raw<CityWithLocation[]>(`select c.*, l.sector_id, l.location_x, l.location_y from cities c
+join locations l on c.id = l.city_id 
+where l.sector_id = ?`, [sector_id]);
     }
 
     async buildBuilding(building: Building, amount: number, city: City): Promise<BuildQueue> {
@@ -139,11 +193,11 @@ export class CityRepository extends Repository<City> {
      * @param city1 
      * @param city2 
      */
-    distanceInSeconds(city1: City, city2: City): number {
+    distanceInSeconds(city1: CityWithLocation, city2: CityWithLocation): number {
         return this.distanceInHours(city1, city2) * 60 * 60;
     }
 
-    distanceInHours(city1: City, city2: City): number {
+    distanceInHours(city1: CityWithLocation, city2: CityWithLocation): number {
         const dist = Math.sqrt(
             Math.pow((city2.location_x - city1.location_x), 2) 
             + 
@@ -219,7 +273,7 @@ export class CityRepository extends Repository<City> {
         return power
     }
 
-    async attack(attacker: City, attacked: City, army: Army): Promise<ArmyQueue> {
+    async attack(attacker: CityWithLocation, attacked: CityWithLocation, army: Army): Promise<ArmyQueue> {
         // validate the user has enough of a military! 
         if(attacker.soldiers < army.soldiers) {
             throw new InsufficientResourceError('soldiers', army.soldiers, attacker.soldiers);