1 | var express = require('express'),
|
2 | router = express.Router(),
|
3 | q = require('q'),
|
4 | _ = require('lodash'),
|
5 | jsonResponse = require('q-json-response'),
|
6 | utils = require('./utils'),
|
7 | common = require('@screeps/common'),
|
8 | config = common.configManager.config,
|
9 | db = common.storage.db,
|
10 | env = common.storage.env,
|
11 | C = config.common.constants,
|
12 | strongholds = require('./strongholds');
|
13 |
|
14 | config.cronjobs = {
|
15 | sendNotifications: [60, sendNotifications],
|
16 | roomsForceUpdate: [20, roomsForceUpdate],
|
17 | genPowerBanks: [5*60, genPowerBanks],
|
18 | genInvaders: [5*60, genInvaders],
|
19 | purgeTransactions: [60*60, purgeTransactions],
|
20 | recreateNpcOrders: [5*60, recreateNpcOrders],
|
21 | calcMarketStats: [60*60, calcMarketStats],
|
22 | deletePowerCreeps: [10*60, deletePowerCreeps],
|
23 | genDeposits: [5*60, genDeposits],
|
24 | genStrongholds: [5*60, strongholds.genStrongholds],
|
25 | expandStrongholds: [15*60, strongholds.expandStrongholds]
|
26 | };
|
27 |
|
28 | module.exports.run = function() {
|
29 | _.forEach(config.cronjobs, (i,key) => {
|
30 | if(Date.now() - (i[2]||0) > i[0]*1000) {
|
31 | console.log(`Running cronjob '${key}'`);
|
32 | i[2] = Date.now();
|
33 | i[1]({utils});
|
34 | }
|
35 | });
|
36 | };
|
37 |
|
38 | function recreateNpcOrders() {
|
39 | var gameTime;
|
40 |
|
41 | var sellMinerals = ['X','Z','K','L','U','O','O','H','H','Z','K','L','U','O','O','H','H'];
|
42 | var buyMinerals = ['X','Z','K','L','U','O','O','H','H','Z','K','L','U','O','O','H','H'];
|
43 | var sellPrice = {
|
44 | H: 3000,
|
45 | O: 3000,
|
46 | Z: 6000,
|
47 | K: 6000,
|
48 | U: 6000,
|
49 | L: 6000,
|
50 | X: 18000
|
51 | };
|
52 | var buyPrice = 1000;
|
53 |
|
54 | var sellMineralAmount = 40, sellEnergyAmount = 40, buyMineralAmount = 20, period = 5000;
|
55 |
|
56 | var cnt = 0;
|
57 |
|
58 | return common.getGametime()
|
59 | .then(data => gameTime = data)
|
60 | .then(() => db['rooms.objects'].find({$and: [{type: 'terminal'}, {user: {$eq: null}}]}))
|
61 | .then(terminals => common.qSequence(terminals, terminal => {
|
62 | return db.rooms.findOne({_id: terminal.room})
|
63 | .then(room => {
|
64 | if(room.status != 'normal') {
|
65 | return;
|
66 | }
|
67 | if(room.nextNpcMarketOrder && room.nextNpcMarketOrder > gameTime) {
|
68 | return;
|
69 | }
|
70 | const nowTimestamp = new Date().getTime();
|
71 | var sellMineral = sellMinerals[Math.floor(Math.random()*sellMinerals.length)];
|
72 | var buyMineral = buyMinerals[Math.floor(Math.random()*buyMinerals.length)];
|
73 | var orders = [];
|
74 |
|
75 | orders.push({
|
76 | created: gameTime,
|
77 | createdTimestamp: nowTimestamp,
|
78 | active: true,
|
79 | type: 'sell',
|
80 | amount: period*sellMineralAmount,
|
81 | remainingAmount: period*sellMineralAmount,
|
82 | totalAmount: period*sellMineralAmount,
|
83 | resourceType: sellMineral,
|
84 | price: sellPrice[sellMineral],
|
85 | roomName: terminal.room
|
86 | });
|
87 |
|
88 | if(Math.random() < 0.5) {
|
89 | orders.push({
|
90 | created: gameTime,
|
91 | createdTimestamp: nowTimestamp,
|
92 | active: true,
|
93 | type: 'sell',
|
94 | amount: period*sellEnergyAmount,
|
95 | remainingAmount: period*sellEnergyAmount,
|
96 | totalAmount: period*sellEnergyAmount,
|
97 | resourceType: 'energy',
|
98 | price: 1000,
|
99 | roomName: terminal.room
|
100 | });
|
101 | }
|
102 | if(Math.random() < 0.25) {
|
103 | orders.push({
|
104 | created: gameTime,
|
105 | createdTimestamp: nowTimestamp,
|
106 | active: true,
|
107 | type: 'buy',
|
108 | amount: period*buyMineralAmount,
|
109 | remainingAmount: period*buyMineralAmount,
|
110 | totalAmount: period*buyMineralAmount,
|
111 | resourceType: buyMineral,
|
112 | price: buyPrice,
|
113 | roomName: terminal.room
|
114 | });
|
115 | }
|
116 | cnt++;
|
117 | return db['market.orders'].removeWhere({roomName: room._id})
|
118 | .then(() => db['market.orders'].insert(orders))
|
119 | .then(() => db.rooms.update({_id: room._id}, {$set: {nextNpcMarketOrder: gameTime + Math.round(period*(0.8 + 0.4*Math.random()))}}));
|
120 | })
|
121 | }))
|
122 | }
|
123 |
|
124 | function sendNotifications() {
|
125 |
|
126 | var notifications, userIds;
|
127 | var filterDate = new Date();
|
128 | return db['users.notifications'].find({date: {$lt: filterDate.getTime()}})
|
129 | .then((data) => {
|
130 | notifications = data;
|
131 | userIds = _(notifications).pluck('user').uniq(false, (i) => i.toString()).value();
|
132 | })
|
133 | .then(() => db.users.find({_id: {$in: userIds}}))
|
134 | .then((users) => {
|
135 | var promise = q.when();
|
136 | users.forEach((user) => {
|
137 |
|
138 | var notificationIdsToRemove = [];
|
139 |
|
140 | promise = promise.then(() => {
|
141 |
|
142 | var userNotifications = _.filter(notifications, (i) => i.user == user._id);
|
143 |
|
144 | if (user.notifyPrefs && (user.notifyPrefs.disabled || !user.email)) {
|
145 | userNotifications.forEach((notification) => {
|
146 | notificationIdsToRemove.push(notification._id);
|
147 | });
|
148 | return;
|
149 | }
|
150 |
|
151 | var interval = 60;
|
152 | if (user.notifyPrefs && user.notifyPrefs.interval) {
|
153 | interval = user.notifyPrefs.interval;
|
154 | }
|
155 | interval *= 60 * 1000;
|
156 |
|
157 | if (user.lastNotifyDate && (user.lastNotifyDate + interval > Date.now())) {
|
158 | return;
|
159 | }
|
160 |
|
161 | userNotifications.forEach((notification) => {
|
162 | notificationIdsToRemove.push(notification._id);
|
163 | });
|
164 |
|
165 | config.backend.emit('sendUserNotifications',user,
|
166 | userNotifications.map(i => _.pick(i, ['message','date','count','type'])));
|
167 | })
|
168 | .catch((e) => console.log(`Error sending a message to ${user.username}: ${e}`))
|
169 | .then(() => notificationIdsToRemove.length > 0 && q.all([
|
170 | db['users.notifications'].removeWhere({_id: {$in: notificationIdsToRemove}}),
|
171 | db.users.update({_id: user._id}, {$set: {lastNotifyDate: Date.now()}})
|
172 | ]))
|
173 | });
|
174 | return promise;
|
175 | })
|
176 | }
|
177 |
|
178 | async function roomsForceUpdate() {
|
179 | const gameTime = await common.getGametime();
|
180 | const activeRooms = await utils.getActiveRooms();
|
181 | const rooms = await db.rooms.find({status: {$ne: 'out of borders'}, _id: {$nin: activeRooms}});
|
182 | for(let room of rooms) {
|
183 | if (!room.nextForceUpdateTime || gameTime >= room.nextForceUpdateTime) {
|
184 | await utils.activateRoom(room._id);
|
185 | await db.rooms.update({_id: room._id}, {
|
186 | $set: {
|
187 | nextForceUpdateTime: gameTime + 90 + Math.floor(Math.random() * 20)
|
188 | }
|
189 | });
|
190 | }
|
191 | }
|
192 | }
|
193 |
|
194 | async function genPowerBanks() {
|
195 | const gameTime = await common.getGametime();
|
196 | const rooms = await db.rooms.find({bus: true, status: 'normal'});
|
197 |
|
198 | for(const room of rooms) {
|
199 | const respawnTime = Math.round(Math.random() * C.POWER_BANK_RESPAWN_TIME / 2 + C.POWER_BANK_RESPAWN_TIME * 0.75);
|
200 | if (!room.powerBankTime) {
|
201 | room.powerBankTime = gameTime + respawnTime;
|
202 | await db.rooms.update({_id: room._id}, {$set: room});
|
203 | continue;
|
204 | }
|
205 |
|
206 | if (gameTime < room.powerBankTime) {
|
207 | continue;
|
208 | }
|
209 |
|
210 | room.powerBankTime = gameTime + respawnTime;
|
211 | const data = await db['rooms.terrain'].findOne({room: room._id});
|
212 | let x, y, isWall, hasExit;
|
213 | do {
|
214 | x = Math.floor(Math.random() * 40 + 5);
|
215 | y = Math.floor(Math.random() * 40 + 5);
|
216 | isWall = parseInt(data.terrain.charAt(y * 50 + x)) & 1;
|
217 | hasExit = false;
|
218 | for (let dx = -1; dx <= 1; dx++) {
|
219 | for (let dy = -1; dy <= 1; dy++) {
|
220 | if (!(parseInt(data.terrain.charAt((y + dy) * 50 + x + dx)) & 1)) {
|
221 | hasExit = true;
|
222 | }
|
223 | }
|
224 | }
|
225 | }
|
226 | while (!isWall || !hasExit);
|
227 | let power = Math.floor(Math.random() * (C.POWER_BANK_CAPACITY_MAX - C.POWER_BANK_CAPACITY_MIN) + C.POWER_BANK_CAPACITY_MIN);
|
228 | if (Math.random() < C.POWER_BANK_CAPACITY_CRIT) {
|
229 | power += C.POWER_BANK_CAPACITY_MAX;
|
230 | }
|
231 | await db['rooms.objects'].insert({
|
232 | type: 'powerBank',
|
233 | x, y,
|
234 | room: room._id,
|
235 | store: {power},
|
236 | hits: C.POWER_BANK_HITS,
|
237 | hitsMax: C.POWER_BANK_HITS,
|
238 | decayTime: gameTime + C.POWER_BANK_DECAY
|
239 | });
|
240 | await db.rooms.update({_id: room._id}, {$set: {powerBankTime: room.powerBankTime}});
|
241 | await utils.activateRoom(room._id);
|
242 | }
|
243 | }
|
244 |
|
245 | async function genInvaders() {
|
246 |
|
247 | function checkExit(roomName, exit) {
|
248 | var [x,y] = utils.roomNameToXY(roomName);
|
249 | if(exit == 'top') y--;
|
250 | if(exit == 'right') x++;
|
251 | if(exit == 'bottom') y++;
|
252 | if(exit == 'left') x--;
|
253 | var newRoomName = utils.roomNameFromXY(x,y);
|
254 | return db['rooms.objects'].findOne({room: newRoomName, type: 'controller'})
|
255 | .then(controller => {
|
256 | if(controller && (controller.user || controller.reservation)) {
|
257 | return q.reject();
|
258 | }
|
259 | })
|
260 | }
|
261 |
|
262 | function createCreep(type, room, square, boosted) {
|
263 |
|
264 | var [x,y] = utils.roomNameToXY(room);
|
265 |
|
266 | var body = {
|
267 | bigHealer: ['move','move','move','move','move','move','move','move','move','move','move','move','move','move','move','move','move','move','move','move','move','move','move','move','heal','heal','heal','heal','heal','heal','heal','heal','heal','heal','heal','heal','heal','heal','heal','heal','heal','heal','heal','heal','heal','heal','heal','heal','heal','move'],
|
268 | bigRanged: ['tough','tough','tough','tough','tough','tough','move','move','move','move','move','move','move','move','move','move','move','move','move','move','move','move','move','move','move','move','move','move','move','move','ranged_attack','ranged_attack','ranged_attack','ranged_attack','ranged_attack','ranged_attack','ranged_attack','ranged_attack','ranged_attack','ranged_attack','ranged_attack','ranged_attack','ranged_attack','ranged_attack','ranged_attack','ranged_attack','ranged_attack','ranged_attack','work','move'],
|
269 | bigMelee: ['tough','tough','tough','tough','tough','tough','tough','tough','tough','tough','tough','tough','tough','tough','tough','tough','move','move','move','move','move','move','move','move','move','move','move','move','move','move','move','move','move','move','move','move','move','move','move','move','ranged_attack','ranged_attack','ranged_attack','work','work','work','work','attack','attack','move'],
|
270 | smallHealer: ['move','move','move','move','heal','heal','heal','heal','heal','move'],
|
271 | smallRanged: ['tough','tough','move','move','move','move','ranged_attack','ranged_attack','ranged_attack','move'],
|
272 | smallMelee: ['tough','tough','move','move','move','move','ranged_attack','work','attack','move']
|
273 | };
|
274 |
|
275 | var creep = {
|
276 | type: 'creep',
|
277 | user: '2',
|
278 | body: _.map(body[type], type => ({type, hits: 100})),
|
279 | hits: body[type].length * 100,
|
280 | hitsMax: body[type].length * 100,
|
281 | ticksToLive: 1500,
|
282 | x: square[0],
|
283 | y: square[1],
|
284 | room,
|
285 | fatigue: 0,
|
286 | store: {},
|
287 | storeCapacity: 0,
|
288 | name: `invader_${room}_${Math.floor(Math.random()*1000)}`
|
289 | };
|
290 |
|
291 | if(boosted) {
|
292 | creep.body.forEach(i => {
|
293 | if(i.type == 'heal') {
|
294 | i.boost = utils.isCenter(x,y) ? 'XLHO2' : 'LO';
|
295 | }
|
296 | if(i.type == 'ranged_attack') {
|
297 | i.boost = utils.isCenter(x,y) ? 'XKHO2' : 'KO';
|
298 | }
|
299 | if(i.type == 'work') {
|
300 | i.boost = utils.isCenter(x,y) ? 'XZH2O' : 'ZH';
|
301 | }
|
302 | if(i.type == 'attack') {
|
303 | i.boost = utils.isCenter(x,y) ? 'XUH2O' : 'UH';
|
304 | }
|
305 | if(i.type == 'tough') {
|
306 | i.boost = utils.isCenter(x,y) ? 'XGHO2' : 'GO';
|
307 | }
|
308 | })
|
309 | }
|
310 |
|
311 | return db['rooms.objects'].insert(creep);
|
312 | }
|
313 |
|
314 | function findClosestExit(square, exits) {
|
315 | var sortedExits = _.sortBy(exits, i => Math.max(Math.abs(i[0] - square[0]), Math.abs(i[1] - square[1])));
|
316 | return sortedExits[0];
|
317 | }
|
318 |
|
319 |
|
320 | function createRaid(controllerLevel, room, exits) {
|
321 |
|
322 | var type = controllerLevel && controllerLevel >= 4 ? 'big' : 'small';
|
323 |
|
324 | var max = 1, count = 1, boostChance = 0.5;
|
325 |
|
326 | var [x,y] = utils.roomNameToXY(room);
|
327 |
|
328 | if(Math.random() > 0.9 || utils.isCenter(x,y)) {
|
329 | max = 2;
|
330 | boostChance = type == 'big' ? 0 : 0.5;
|
331 | if (Math.random() > 0.8 || utils.isCenter(x,y)) {
|
332 | max = 5;
|
333 | if (type == 'big') {
|
334 | if (controllerLevel < 5) {
|
335 | max = 2;
|
336 | }
|
337 | else if (controllerLevel < 6) {
|
338 | max = 2;
|
339 | }
|
340 | else if (controllerLevel < 7) {
|
341 | max = 3;
|
342 | }
|
343 | else if (controllerLevel < 8) {
|
344 | boostChance = 0.4;
|
345 | max = 3;
|
346 | }
|
347 | else {
|
348 | boostChance = 0.4;
|
349 | max = 5;
|
350 | }
|
351 | }
|
352 | }
|
353 |
|
354 | count = Math.floor(Math.random()*(max-1)) + 2;
|
355 | count = Math.min(count, exits.length);
|
356 | }
|
357 |
|
358 | var promises = [], lastSquare;
|
359 |
|
360 | for(var i=0; i<count; i++) {
|
361 | var subtype = i == 0 && !utils.isCenter(x,y) ? 'Melee' :
|
362 | i == 0 || (i == 1 || i == 2 && count == 5) && Math.random() > 0.5 ? 'Ranged' :
|
363 | 'Healer';
|
364 |
|
365 | var square = lastSquare ? findClosestExit(lastSquare, exits) : exits[Math.floor(Math.random() * exits.length)];
|
366 | if(!square) {
|
367 | break;
|
368 | }
|
369 | promises.push(createCreep(type + subtype, room, square, Math.random() < boostChance));
|
370 | _.pull(exits, square);
|
371 | lastSquare = square;
|
372 | }
|
373 |
|
374 | return q.all(promises);
|
375 | }
|
376 |
|
377 | const activeRooms = await utils.getActiveRooms();
|
378 | const rooms = await db.rooms.find({status: 'normal', _id: {$nin: activeRooms}});
|
379 | for(const room of rooms) {
|
380 | const objects = await db['rooms.objects'].find({room: room._id, type: {$in: ['source','controller','creep']}});
|
381 | const creeps = _.filter(objects, {type: 'creep', user: '2'});
|
382 | if(creeps.length) {
|
383 | continue;
|
384 | }
|
385 |
|
386 | const sources = _.filter(objects, {type: 'source'});
|
387 | const invaderHarvested = _.sum(sources, 'invaderHarvested');
|
388 | const goal = room.invaderGoal || C.INVADERS_ENERGY_GOAL;
|
389 | if(goal != 1 && invaderHarvested < goal) {
|
390 | continue;
|
391 | }
|
392 |
|
393 | const sectorRegex = room._id.replace(/^([WE]\d*)\d([NS]\d*)\d$/, (str, p1, p2) => `^${p1}\\d${p2}\\d$`);
|
394 | const invaderCore = await db['rooms.objects'].count({type: 'invaderCore', level: {$gt: 0}, room: {$regex: sectorRegex}});
|
395 | if(!invaderCore) {
|
396 | console.log(`Skip room ${room._id} since there is no invaderCore in sector regex ${sectorRegex}`);
|
397 | continue;
|
398 | }
|
399 |
|
400 | const terrain = await db['rooms.terrain'].findOne({room: room._id});
|
401 | let exits = {}, exitSquares = {top: [], left: [], right: [], bottom: []};
|
402 | for(let i=0; i<49; i++) {
|
403 | if(!common.checkTerrain(terrain.terrain, i, 0, C.TERRAIN_MASK_WALL)) {
|
404 | exits.top = true;
|
405 | exitSquares.top.push([i,0]);
|
406 | }
|
407 | if(!common.checkTerrain(terrain.terrain, i, 49, C.TERRAIN_MASK_WALL)) {
|
408 | exits.bottom = true;
|
409 | exitSquares.bottom.push([i,49]);
|
410 | }
|
411 | if(!common.checkTerrain(terrain.terrain, 0, i, C.TERRAIN_MASK_WALL)) {
|
412 | exits.left = true;
|
413 | exitSquares.left.push([0,i]);
|
414 | }
|
415 | if(!common.checkTerrain(terrain.terrain, 49, i, C.TERRAIN_MASK_WALL)) {
|
416 | exits.right = true;
|
417 | exitSquares.right.push([49,i]);
|
418 | }
|
419 | }
|
420 | exits = _.keys(exits);
|
421 |
|
422 | await q.all(_.map(exits, exit => {
|
423 | return checkExit(room._id, exit).catch(() => _.pull(exits, exit));
|
424 | }));
|
425 |
|
426 | if(!exits.length) {
|
427 | continue;
|
428 | }
|
429 | const exit = exits[Math.floor(Math.random()*exits.length)];
|
430 | const controller = _.find(objects, {type: 'controller'});
|
431 | await createRaid(controller && controller.user && controller.level, room._id, exitSquares[exit]);
|
432 |
|
433 | let invaderGoal = Math.floor(C.INVADERS_ENERGY_GOAL * (Math.random()*0.6 + 0.7));
|
434 | if(Math.random() < 0.1) {
|
435 | invaderGoal *= Math.floor( Math.random() > 0.5 ? 2 : 0.5 );
|
436 | }
|
437 | await db.rooms.update({_id: room._id}, {$set: {invaderGoal}})
|
438 | await db['rooms.objects'].update({room: room._id, type: 'source'}, {$set: {invaderHarvested: 0}});
|
439 | await utils.activateRoom(room._id);
|
440 | }
|
441 | }
|
442 |
|
443 | function purgeTransactions() {
|
444 | return db.transactions.find()
|
445 | .then(data => {
|
446 | data = _.sortBy(data, i => -parseInt(i.time));
|
447 |
|
448 | var senders = {}, recipients = {}, forDelete = [];
|
449 | data.forEach(i => {
|
450 | var flag1 = true, flag2 = true;
|
451 | senders[i.sender] = senders[i.sender] || [];
|
452 | if(senders[i.sender].length < 100) {
|
453 | senders[i.sender].push(i);
|
454 | flag1 = false;
|
455 | }
|
456 | recipients[i.recipient] = recipients[i.recipient] || [];
|
457 | if(recipients[i.recipient].length < 100) {
|
458 | recipients[i.recipient].push(i);
|
459 | flag2 = false;
|
460 | }
|
461 | if(flag1 && flag2) {
|
462 | forDelete.push(i._id);
|
463 | }
|
464 | });
|
465 |
|
466 | if(forDelete.length > 0) {
|
467 | return db.transactions.removeWhere({_id: {$in: forDelete}});
|
468 | }
|
469 | })
|
470 | }
|
471 |
|
472 | function calcMarketStats() {
|
473 | return db['market.stats'].removeWhere({})
|
474 | .then(()=>db['users.money'].find({$and: [{date: {$gt: new Date(Date.now() - 14 * 24 * 3600 * 1000)}},{type: 'market.sell'}]}))
|
475 | .then(data => {
|
476 | const result = {};
|
477 |
|
478 | data.forEach(i => {
|
479 | const date = new Date(i.date);
|
480 | i.dateStr = `${date.getFullYear()}-${date.getMonth()<9?'0':''}${date.getMonth()+1}-${date.getDate()<10?'0':''}${date.getDate()}`;
|
481 | const r = i.market.resourceType;
|
482 | if (!result[r]) {
|
483 | result[r] = {};
|
484 | }
|
485 | if (!result[r][i.dateStr]) {
|
486 | result[r][i.dateStr] = {sumPrice: 0, sumAmount: 0, stddev: 0, cnt: 0};
|
487 | }
|
488 | result[r][i.dateStr].sumPrice += i.change;
|
489 | result[r][i.dateStr].sumAmount += i.market.amount;
|
490 | result[r][i.dateStr].cnt++;
|
491 | });
|
492 |
|
493 | for (let resourceType in result) {
|
494 | for(let date in result[resourceType]) {
|
495 | result[resourceType][date].avg = result[resourceType][date].sumPrice / result[resourceType][date].sumAmount;
|
496 | }
|
497 | }
|
498 |
|
499 | data.forEach(i => {
|
500 | result[i.market.resourceType][i.dateStr].stddev += i.market.amount *
|
501 | Math.pow(i.market.price - result[i.market.resourceType][i.dateStr].avg, 2) /
|
502 | result[i.market.resourceType][i.dateStr].sumAmount;
|
503 | });
|
504 |
|
505 | const promises = [];
|
506 |
|
507 | for (let resourceType in result) {
|
508 | for (let date in result[resourceType]) {
|
509 | const i = result[resourceType][date];
|
510 | promises.push(db['market.stats'].insert({
|
511 | resourceType,
|
512 | date,
|
513 | transactions: i.cnt,
|
514 | volume: i.sumAmount,
|
515 | avgPrice: +i.avg.toFixed(3),
|
516 | stddevPrice: +Math.sqrt(i.stddev).toFixed(3)
|
517 | }));
|
518 | }
|
519 | }
|
520 |
|
521 | return q.all(promises)
|
522 | }).catch(console.log);
|
523 | };
|
524 |
|
525 | function calcPowerLevelBase(level) {
|
526 | return Math.pow(level, C.POWER_LEVEL_POW) * C.POWER_LEVEL_MULTIPLY;
|
527 | }
|
528 |
|
529 | function calcPowerLevel(power) {
|
530 | return Math.floor( Math.pow( (power || 0) / C.POWER_LEVEL_MULTIPLY, 1 / C.POWER_LEVEL_POW ) );
|
531 | }
|
532 |
|
533 | function deletePowerCreeps() {
|
534 | return db['users.power_creeps'].find({deleteTime: {$lt: Date.now()}})
|
535 | .then((data) => _.reduce(data, (promise, creep) => {
|
536 | if(!creep.deleteTime) {
|
537 | return promise;
|
538 | }
|
539 | return promise
|
540 | .then(() => db['users'].findOne({_id: creep.user}))
|
541 | .then(user => {
|
542 | var level = calcPowerLevel(user.power);
|
543 | var basePrev = calcPowerLevelBase(level-1);
|
544 | var baseCurrent = calcPowerLevelBase(level);
|
545 | var baseNext = calcPowerLevelBase(level+1);
|
546 | var change = Math.round(user.power - basePrev -
|
547 | (user.power - baseCurrent) * (baseCurrent - basePrev) / (baseNext - baseCurrent));
|
548 | return q.all([
|
549 | db['users'].update({_id: user._id}, {$inc: {power: -change}}),
|
550 | db['users.power_creeps'].removeWhere({_id: creep._id})
|
551 | ]);
|
552 | })
|
553 | }, q.when()));
|
554 | }
|
555 |
|
556 | async function genDeposits(){
|
557 | function getSectorEdges(centralRoomName) {
|
558 | const result = /^([WE])(\d*)5([NS])(\d*)5$/.exec(centralRoomName);
|
559 | if(!result) {
|
560 | return undefined;
|
561 | }
|
562 | const roomNames = [];
|
563 | const [, we, xx, ns, yy] = result;
|
564 | for(let i = 0; i <= 10; i++) {
|
565 | roomNames.push(`${we}${10*xx+i}${ns}${10*yy}`);
|
566 | roomNames.push(`${we}${10*xx+10}${ns}${10*yy+i}`);
|
567 | roomNames.push(`${we}${10*xx}${ns}${10*yy+i}`);
|
568 | roomNames.push(`${we}${10*xx+i}${ns}${10*yy+10}`);
|
569 | }
|
570 | return _.unique(roomNames);
|
571 | }
|
572 |
|
573 | const gameTime = await common.getGametime();
|
574 | const centralRooms = await db['rooms'].find({_id: {$regex: '^[WE]\d*5[NS]\d*5$'}, status: {$ne: 'out of borders'}});
|
575 | const deposits = await db['rooms.objects'].find({type: 'deposit'});
|
576 |
|
577 | for(const center of centralRooms) {
|
578 | const sectorEdges = getSectorEdges(center._id);
|
579 | const [cx, cy] = utils.roomNameToXY(center._id); const [cxx, cyy] = [24+50*cx, 24+50*cy];
|
580 | const sectorDeposits = _.filter(deposits, d => {
|
581 | if(!_.includes(sectorEdges, d.room)) {
|
582 | return false;
|
583 | }
|
584 |
|
585 |
|
586 | const [xx, yy] = utils.roomNameToXY(d.room);
|
587 | return (Math.abs(50*xx + d.x - cxx) < 250) && (Math.abs(50*yy+d.y - cyy) < 250);
|
588 | });
|
589 | const throughput = _.sum(sectorDeposits, deposit => 20/Math.max(1,(C.DEPOSIT_EXHAUST_MULTIPLY*Math.pow(deposit.harvested||0,C.DEPOSIT_EXHAUST_POW))));
|
590 | if(throughput >= 2.5) {
|
591 | continue;
|
592 | }
|
593 |
|
594 | const rooms = await db['rooms'].find({_id: {$in: sectorEdges}, bus: true, status: 'normal', depositType: {$exists: true}});
|
595 | if(!_.some(rooms)) {
|
596 | return q.reject(`No normal bus rooms found for the sector of ${center._id}} (${sectorRegex})`);
|
597 | }
|
598 | const busyRooms = sectorDeposits.map(i => i.room);
|
599 | const freeRooms = _.reject(rooms, r => _.includes(busyRooms, r._id));
|
600 |
|
601 | if(!_.some(freeRooms)) {
|
602 | return;
|
603 | }
|
604 |
|
605 | const room = _.sample(freeRooms);
|
606 | const terrain = await db['rooms.terrain'].findOne({room: room._id});
|
607 | if(!terrain) {
|
608 | console.log(`${room._id}: no terrain`);
|
609 | return;
|
610 | }
|
611 |
|
612 | const objects = await db['rooms.objects'].find({room: room._id});
|
613 | const [xx, yy] = utils.roomNameToXY(room._id);
|
614 | let x, y, isWall, hasExit, inSector, nearObjects, cnt=0;
|
615 | do {
|
616 | cnt++;
|
617 | x = Math.floor(Math.random() * 40 + 5);
|
618 | y = Math.floor(Math.random() * 40 + 5);
|
619 | isWall = parseInt(terrain.terrain.charAt(y * 50 + x)) & 1;
|
620 | hasExit = false;
|
621 | inSector = (Math.abs(50*xx + x - cxx) < 250) && (Math.abs(50*yy+y - cyy) < 250);
|
622 | for (let dx = -1; dx <= 1; dx++) {
|
623 | for (let dy = -1; dy <= 1; dy++) {
|
624 | if (!(parseInt(terrain.terrain.charAt((y + dy) * 50 + x + dx)) & 1)) {
|
625 | hasExit = true;
|
626 | }
|
627 | }
|
628 | }
|
629 | nearObjects = _.any(objects, obj => Math.abs(obj.x-x) <= 2 && Math.abs(obj.y-y) <= 2);
|
630 | }
|
631 | while ((!isWall || !hasExit || nearObjects) && cnt < 1000);
|
632 | if(cnt >= 1000) {
|
633 | console.log(`cannot find location in ${room._id}`);
|
634 | continue;
|
635 | }
|
636 |
|
637 | if(room.depositType) {
|
638 | const obj = {type: 'deposit', depositType: room.depositType, x, y, room: room._id, harvested: 0, decayTime: C.DEPOSIT_DECAY_TIME + gameTime};
|
639 | await db['rooms.objects'].insert(obj);
|
640 | await utils.activateRoom(room._id);
|
641 | }
|
642 | }
|
643 | }
|