ability to cancel construction and have a portion of the funds returned
[browser-rts.git] / src / repository / city.ts
index 43e1b7a4247e9e91a109f94b6774cdb96a740465..c9ecb91b0aa358d96c9de6d4487087b328cd2670 100644 (file)
@@ -4,8 +4,8 @@ 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 { UnitTrainingQueue, UnitTrainingQueueRepository, UnitTrainingQueueWithName } from './training-queue';
+import { coalesce, pluck } from '../lib/util';
 import { Building, BuildingRepository } from './buildings';
 import { Unit, UnitRepository } from './unit';
 import _, { random } from 'lodash';
@@ -26,11 +26,19 @@ export type City = {
     defenders: number;
     sp_attackers: number;
     sp_defenders: number;
+    homes: number;
     farms: number;
+    warehouses: number;
+    solar_panels: number;
+    accumulators: number;
+    mining_facilities: number;
+    ore_refinery: number;
     barracks: number;
     special_attacker_trainer: number;
     special_defender_trainer: number;
     icon: string;
+    max_construction_queue: number;
+    max_training_queue: number;
 }
 
 export type CityWithLocation = {
@@ -72,10 +80,18 @@ export class CityRepository extends Repository<City> {
             defenders: 0,
             sp_attackers: 0,
             sp_defenders: 0,
-            farms: 0,
+            homes: 20,
+            farms: 5,
+            warehouses: 5,
+            solar_panels: 5,
+            accumulators: 5,
+            mining_facilities: 5,
+            ore_refinery: 5,
             barracks: 0,
             special_attacker_trainer: 0,
             special_defender_trainer: 0,
+            max_construction_queue: 2,
+            max_training_queue: 2,
             icon
         };
 
@@ -119,8 +135,25 @@ export class CityRepository extends Repository<City> {
     }
 
     async save(city: Partial<City>) {
-        await this.Save(city, {id: city.id});
-        return 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',
+        'homes', 'farms', 'barracks', 'special_attacker_trainer', 'special_defender_trainer'
+      ];
+      
+      const finalData = {};
+
+      fieldsToSave.forEach(field => {
+        if(city.hasOwnProperty(field)) {
+          finalData[field] = city[field];
+        }
+      });
+
+      await this.Save(finalData, {id: city.id});
+      return city;
     }
 
     async findById(cityId: string): Promise<CityWithLocation> {
@@ -173,6 +206,12 @@ where l.sector_id = ?`, [sector_id]);
             throw new InsufficientResourceError('energy', building.energy, city.energy);
         }
 
+        // validate that they have enough empty construction queues
+        const concurrentConstruction = await this.buildQueue.list(city.owner);
+        if(concurrentConstruction.length >= city.max_construction_queue) {
+          throw new InsufficientResourceError('Construction queues', concurrentConstruction.length + 1, city.max_construction_queue);
+        }
+
         city.usedSpace += (building.land * amount);
         city.credits -= (building.credits * amount);
         city.alloys -= (building.alloys * amount);
@@ -239,21 +278,40 @@ where l.sector_id = ?`, [sector_id]);
             throw new InsufficientResourceError('defenders', unit.defenders, city.defenders);
         }
 
-        // validate that they have enough of the buildings to support this
+        // validate that they have enough empty training queues
+        const concurrentTraining = await this.unitTrainigQueue.list(city.owner);
+        if(concurrentTraining.length >= city.max_training_queue) {
+          throw new InsufficientResourceError('Training queues', concurrentTraining.length + 1, city.max_training_queue);
+        }
 
         // ok they have everything, lets update their city 
         // and create the entry in the training queue
 
         city.credits -= unit.credits * amount;
         city.food -= unit.food * amount;
-        city.population -= coalesce(unit.population, 0) * amount;
-        city.soldiers -= coalesce(unit.soldiers, 0) * amount;
-        city.attackers -= coalesce(unit.attackers, 0) * amount;
-        city.defenders -= coalesce(unit.defenders, 0) * amount;
+        city.population -= unit.population * amount;
+        city.soldiers -= unit.soldiers * amount;
+        city.attackers -= unit.attackers * amount;
+        city.defenders -= unit.defenders * amount;
+
+        console.log(city);
 
         await this.save(city);
 
-        const due = Duration.fromObject({ hours: unit.time});
+        // barracks can drop this by 0.01% for each barrack.
+
+        let additionalOffset = 0;
+        if(unit.slug === 'sp_attackers') {
+          additionalOffset = (this.spAttackerTraininerBoost(city) * unit.time);
+        }
+        else if(unit.slug === 'sp_defenders') {
+          additionalOffset = (this.spDefenderTraininerBoost(city) * unit.time);
+        }
+
+        const barracksOffset = _.round((this.barracksImprovement(city) * unit.time) + unit.time - additionalOffset, 2);
+
+        const due = Duration.fromObject({ hours: barracksOffset });
+
         const queue = await this.unitTrainigQueue.create(
             city.owner, 
             DateTime.now().plus({ milliseconds: due.as('milliseconds') }).toMillis(), 
@@ -279,12 +337,38 @@ where l.sector_id = ?`, [sector_id]);
         return power
     }
 
+    barracksImprovement(city: City): number {
+      return city.barracks * 0.0001;
+    }
+
+    spAttackerTraininerBoost(city: City): number {
+      return city.special_attacker_trainer * 0.002;
+    }
+
+    spDefenderTraininerBoost(city: City): number {
+      return city.special_defender_trainer * 0.002;
+    }
+
+    maxPopulation(city: City): number {
+      return city.homes * 25;
+    }
+
+    maxFood(city: City): number {
+      return city.warehouses * 250;
+    }
+
+    maxEnergy(city: City): number {
+      return city.accumulators * 150;
+    }
+
+    maxAlloy(city: City): number {
+      return city.ore_refinery * 75;
+    }
+
     async foodProductionPerTick(city: City): Promise<number> {
       // eventually we should supply the warehouse formula 
       // to calculate the max amount of food created per tick
-      return _.max([
-        city.population + _.round(city.farms * 50)
-      ])
+      return city.farms * 50;
     }
 
     async foodUsagePerTick(city: City): Promise<number> {
@@ -298,12 +382,25 @@ where l.sector_id = ?`, [sector_id]);
       )
     }
 
-    async energyProductionPerTic(city: City): Promise<number> {
-      return 0;
+    async energyProductionPerTick(city: City): Promise<number> {
+      return city.solar_panels * 125;
     }
 
     async energyUsagePerTick(city: City): Promise<number> {
-      return 0;
+      const buildings = await this.buildingRepository.list();
+      const buildingsMap = pluck<Building>(buildings, 'slug');
+      const totalEnergy = Math.ceil(_.sum([
+        city.farms * (buildingsMap['farms'].energy * 0.1),
+        city.barracks * (buildingsMap['barracks'].energy * 0.1),
+        city.special_defender_trainer * (buildingsMap['special_defender_trainer'].energy * 0.1),
+        city.special_attacker_trainer * (buildingsMap['special_attacker_trainer'].energy * 0.1),
+        city.homes * (buildingsMap['homes'].energy * 0.1),
+        city.warehouses * (buildingsMap['warehouses'].energy * 0.1),
+        city.solar_panels * (buildingsMap['solar_panels'].energy * 0.1),
+        city.mining_facilities * (buildingsMap['mining_facilities'].energy * 0.1),
+        city.ore_refinery * (buildingsMap['ore_refinery'].energy * 0.1)
+      ]));
+      return totalEnergy;
     }
 
     async attack(attacker: CityWithLocation, attacked: CityWithLocation, army: Army): Promise<ArmyQueue> {
@@ -346,7 +443,7 @@ where l.sector_id = ?`, [sector_id]);
         return this.buildQueue.list(owner);
     }
 
-    async getUnitTrainingQueues(owner: string): Promise<UnitTrainingQueue[]> {
+    async getUnitTrainingQueues(owner: string): Promise<UnitTrainingQueueWithName[]> {
         return this.unitTrainigQueue.list(owner);
     }
 }