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 |
|
13 | config.cronjobs = {
|
14 | sendNotifications: [60, sendNotifications],
|
15 | roomsForceUpdate: [20, roomsForceUpdate],
|
16 | genPowerBanks: [5*60, genPowerBanks],
|
17 | genInvaders: [5*60, genInvaders],
|
18 | purgeTransactions: [60*60, purgeTransactions],
|
19 | recreateNpcOrders: [5*60, recreateNpcOrders],
|
20 | calcMarketStats: [60*60, calcMarketStats]
|
21 | };
|
22 |
|
23 | module.exports.run = function() {
|
24 | _.forEach(config.cronjobs, (i,key) => {
|
25 | if(Date.now() - (i[2]||0) > i[0]*1000) {
|
26 | console.log(`Running cronjob '${key}'`);
|
27 | i[2] = Date.now();
|
28 | i[1]();
|
29 | }
|
30 | });
|
31 | };
|
32 |
|
33 | function isBus(coord) {
|
34 | return coord < 0 && (coord+1) % 10 == 0 || coord > 0 && (coord) % 10 == 0 || coord == 0;
|
35 | }
|
36 |
|
37 | function isCenter(x,y) {
|
38 | return (x < 0 && Math.abs(x+1)%10 >= 4 && Math.abs(x+1)%10 <= 6 || x >= 0 && Math.abs(x)%10 >= 4 && Math.abs(x)%10 <= 6) &&
|
39 | (y < 0 && Math.abs(y+1)%10 >= 4 && Math.abs(y+1)%10 <= 6 || y >= 0 && Math.abs(y)%10 >= 4 && Math.abs(y)%10 <= 6);
|
40 | }
|
41 |
|
42 | function recreateNpcOrders() {
|
43 | var gameTime;
|
44 |
|
45 | var sellMinerals = ['X','Z','K','L','U','O','O','H','H','Z','K','L','U','O','O','H','H'];
|
46 | var buyMinerals = ['X','Z','K','L','U','O','O','H','H','Z','K','L','U','O','O','H','H'];
|
47 | var sellPrice = {
|
48 | H: 3,
|
49 | O: 3,
|
50 | Z: 6,
|
51 | K: 6,
|
52 | U: 6,
|
53 | L: 6,
|
54 | X: 18
|
55 | };
|
56 | var buyPrice = 1;
|
57 |
|
58 | var sellMineralAmount = 40, sellEnergyAmount = 40, buyMineralAmount = 20, period = 5000;
|
59 |
|
60 | var cnt = 0;
|
61 |
|
62 | return common.getGametime()
|
63 | .then(data => gameTime = data)
|
64 | .then(() => db['rooms.objects'].find({$and: [{type: 'terminal'}, {user: {$eq: null}}]}))
|
65 | .then(terminals => common.qSequence(terminals, terminal => {
|
66 | return db.rooms.findOne({_id: terminal.room})
|
67 | .then(room => {
|
68 | if(room.status != 'normal') {
|
69 | return;
|
70 | }
|
71 | if(room.nextNpcMarketOrder && room.nextNpcMarketOrder > gameTime) {
|
72 | return;
|
73 | }
|
74 | var sellMineral = sellMinerals[Math.floor(Math.random()*sellMinerals.length)];
|
75 | var buyMineral = buyMinerals[Math.floor(Math.random()*buyMinerals.length)];
|
76 | var orders = [];
|
77 |
|
78 | orders.push({
|
79 | created: gameTime,
|
80 | active: true,
|
81 | type: 'sell',
|
82 | amount: period*sellMineralAmount,
|
83 | remainingAmount: period*sellMineralAmount,
|
84 | totalAmount: period*sellMineralAmount,
|
85 | resourceType: sellMineral,
|
86 | price: sellPrice[sellMineral],
|
87 | roomName: terminal.room
|
88 | });
|
89 |
|
90 | if(Math.random() < 0.5) {
|
91 | orders.push({
|
92 | created: gameTime,
|
93 | active: true,
|
94 | type: 'sell',
|
95 | amount: period*sellEnergyAmount,
|
96 | remainingAmount: period*sellEnergyAmount,
|
97 | totalAmount: period*sellEnergyAmount,
|
98 | resourceType: 'energy',
|
99 | price: 1,
|
100 | roomName: terminal.room
|
101 | });
|
102 | }
|
103 | if(Math.random() < 0.25) {
|
104 | orders.push({
|
105 | created: gameTime,
|
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 = 5;
|
152 | if (user.notifyPrefs && user.notifyPrefs.interval > 5) {
|
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 | function roomsForceUpdate() {
|
179 | return common.getGametime()
|
180 | .then(gameTime => {
|
181 | return db.rooms.find({$and: [{status: {$ne: 'out of borders'}}, {active: false}]})
|
182 | .then(rooms => common.qSequence(rooms, room => {
|
183 | if (!room.nextForceUpdateTime || gameTime >= room.nextForceUpdateTime) {
|
184 | return db.rooms.update({_id: room._id}, {
|
185 | $set: {
|
186 | active: true,
|
187 | nextForceUpdateTime: gameTime + 90 + Math.floor(Math.random() * 20)
|
188 | }
|
189 | });
|
190 | }
|
191 | }))
|
192 | });
|
193 | }
|
194 |
|
195 | function genPowerBanks() {
|
196 |
|
197 |
|
198 |
|
199 | |
200 |
|
201 |
|
202 |
|
203 |
|
204 |
|
205 |
|
206 |
|
207 |
|
208 |
|
209 |
|
210 |
|
211 |
|
212 |
|
213 |
|
214 |
|
215 |
|
216 |
|
217 |
|
218 |
|
219 |
|
220 |
|
221 |
|
222 |
|
223 |
|
224 |
|
225 |
|
226 |
|
227 |
|
228 |
|
229 |
|
230 |
|
231 |
|
232 |
|
233 |
|
234 |
|
235 |
|
236 |
|
237 |
|
238 |
|
239 |
|
240 |
|
241 |
|
242 |
|
243 |
|
244 |
|
245 |
|
246 |
|
247 |
|
248 |
|
249 |
|
250 |
|
251 |
|
252 | }
|
253 |
|
254 | function genInvaders() {
|
255 |
|
256 | function checkExit(roomName, exit) {
|
257 | var [x,y] = utils.roomNameToXY(roomName);
|
258 | if(exit == 'top') y--;
|
259 | if(exit == 'right') x++;
|
260 | if(exit == 'bottom') y++;
|
261 | if(exit == 'left') x--;
|
262 | var newRoomName = utils.roomNameFromXY(x,y);
|
263 | return db['rooms.objects'].findOne({$and: [{room: newRoomName}, {type: 'controller'}]})
|
264 | .then(controller => {
|
265 | if(controller && (controller.user || controller.reservation)) {
|
266 | return q.reject();
|
267 | }
|
268 | })
|
269 | }
|
270 |
|
271 | function createCreep(type, room, square, boosted) {
|
272 |
|
273 | var [x,y] = utils.roomNameToXY(room);
|
274 |
|
275 | var body = {
|
276 | 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'],
|
277 | 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'],
|
278 | 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'],
|
279 | smallHealer: ['move','move','move','move','heal','heal','heal','heal','heal','move'],
|
280 | smallRanged: ['tough','tough','move','move','move','move','ranged_attack','ranged_attack','ranged_attack','move'],
|
281 | smallMelee: ['tough','tough','move','move','move','move','ranged_attack','work','attack','move']
|
282 | };
|
283 |
|
284 | var creep = {
|
285 | type: 'creep',
|
286 | user: '2',
|
287 | body: _.map(body[type], type => ({type, hits: 100})),
|
288 | hits: body[type].length * 100,
|
289 | hitsMax: body[type].length * 100,
|
290 | ticksToLive: 1500,
|
291 | x: square[0],
|
292 | y: square[1],
|
293 | room,
|
294 | fatigue: 0,
|
295 | energy: 0,
|
296 | energyCapacity: 0,
|
297 | name: `invader_${room}_${Math.floor(Math.random()*1000)}`
|
298 | };
|
299 |
|
300 | if(boosted) {
|
301 | creep.body.forEach(i => {
|
302 | if(i.type == 'heal') {
|
303 | i.boost = isCenter(x,y) ? 'XLHO2' : 'LO';
|
304 | }
|
305 | if(i.type == 'ranged_attack') {
|
306 | i.boost = isCenter(x,y) ? 'XKHO2' : 'KO';
|
307 | }
|
308 | if(i.type == 'work') {
|
309 | i.boost = isCenter(x,y) ? 'XZH2O' : 'ZH';
|
310 | }
|
311 | if(i.type == 'attack') {
|
312 | i.boost = isCenter(x,y) ? 'XUH2O' : 'UH';
|
313 | }
|
314 | if(i.type == 'tough') {
|
315 | i.boost = isCenter(x,y) ? 'XGHO2' : 'GO';
|
316 | }
|
317 | })
|
318 | }
|
319 |
|
320 | return db['rooms.objects'].insert(creep);
|
321 | }
|
322 |
|
323 | function findClosestExit(square, exits) {
|
324 | var sortedExits = _.sortBy(exits, i => Math.max(Math.abs(i[0] - square[0]), Math.abs(i[1] - square[1])));
|
325 | return sortedExits[0];
|
326 | }
|
327 |
|
328 |
|
329 | function createRaid(controllerLevel, room, exits) {
|
330 |
|
331 | var type = controllerLevel && controllerLevel >= 4 ? 'big' : 'small';
|
332 |
|
333 | var max = 1, count = 1, boostChance = 0.5;
|
334 |
|
335 | var [x,y] = utils.roomNameToXY(room);
|
336 |
|
337 | if(Math.random() > 0.9 || isCenter(x,y)) {
|
338 | max = 2;
|
339 | boostChance = type == 'big' ? 0 : 0.5;
|
340 | if (Math.random() > 0.8 || isCenter(x,y)) {
|
341 | max = 5;
|
342 | if (type == 'big') {
|
343 | if (controllerLevel < 5) {
|
344 | max = 2;
|
345 | }
|
346 | else if (controllerLevel < 6) {
|
347 | max = 2;
|
348 | }
|
349 | else if (controllerLevel < 7) {
|
350 | max = 3;
|
351 | }
|
352 | else if (controllerLevel < 8) {
|
353 | boostChance = 0.4;
|
354 | max = 3;
|
355 | }
|
356 | else {
|
357 | boostChance = 0.4;
|
358 | max = 5;
|
359 | }
|
360 | }
|
361 | }
|
362 |
|
363 | count = Math.floor(Math.random()*(max-1)) + 2;
|
364 | count = Math.min(count, exits.length);
|
365 | }
|
366 |
|
367 | var promises = [], lastSquare;
|
368 |
|
369 | for(var i=0; i<count; i++) {
|
370 | var subtype = i == 0 && !isCenter(x,y) ? 'Melee' :
|
371 | i == 0 || (i == 1 || i == 2 && count == 5) && Math.random() > 0.5 ? 'Ranged' :
|
372 | 'Healer';
|
373 |
|
374 | var square = lastSquare ? findClosestExit(lastSquare, exits) : exits[Math.floor(Math.random() * exits.length)];
|
375 | if(!square) {
|
376 | break;
|
377 | }
|
378 | promises.push(createCreep(type + subtype, room, square, Math.random() < boostChance));
|
379 | _.pull(exits, square);
|
380 | lastSquare = square;
|
381 | }
|
382 |
|
383 | return q.all(promises);
|
384 | }
|
385 |
|
386 | return db.rooms.find({$and: [{status: 'normal'}, {active: true}]})
|
387 | .then(rooms => q.all(rooms.map(room => {
|
388 |
|
389 | return db['rooms.objects'].find({$and: [{room: room._id}, {type: {$in: ['source','controller','creep']}}]})
|
390 | .then(objects => {
|
391 | var sources = _.filter(objects, {type: 'source'});
|
392 | var creeps = _.filter(objects, {type: 'creep', user: '2'});
|
393 | if(creeps.length) {
|
394 | return;
|
395 | }
|
396 | var invaderHarvested = _.sum(sources, 'invaderHarvested');
|
397 | var goal = room.invaderGoal || C.INVADERS_ENERGY_GOAL;
|
398 | if(goal != 1 && invaderHarvested < goal) {
|
399 | return;
|
400 | }
|
401 | return db['rooms.terrain'].findOne({room: room._id})
|
402 | .then(terrain => {
|
403 | var exits = {}, exitSquares = {top: [], left: [], right: [], bottom: []};
|
404 | for(var i=0; i<49; i++) {
|
405 | if(!common.checkTerrain(terrain.terrain, i, 0, C.TERRAIN_MASK_WALL)) {
|
406 | exits.top = true;
|
407 | exitSquares.top.push([i,0]);
|
408 | }
|
409 | if(!common.checkTerrain(terrain.terrain, i, 49, C.TERRAIN_MASK_WALL)) {
|
410 | exits.bottom = true;
|
411 | exitSquares.bottom.push([i,49]);
|
412 | }
|
413 | if(!common.checkTerrain(terrain.terrain, 0, i, C.TERRAIN_MASK_WALL)) {
|
414 | exits.left = true;
|
415 | exitSquares.left.push([0,i]);
|
416 | }
|
417 | if(!common.checkTerrain(terrain.terrain, 49, i, C.TERRAIN_MASK_WALL)) {
|
418 | exits.right = true;
|
419 | exitSquares.right.push([49,i]);
|
420 | }
|
421 | }
|
422 | exits = _.keys(exits);
|
423 | return q.all(_.map(exits, exit => {
|
424 | return checkExit(room._id, exit).catch(() => _.pull(exits, exit));
|
425 | }))
|
426 | .then(() => {
|
427 | if(!exits.length) {
|
428 | return;
|
429 | }
|
430 | var exit = exits[Math.floor(Math.random()*exits.length)];
|
431 | var controller = _.find(objects, {type: 'controller'});
|
432 | return createRaid(controller && controller.user && controller.level, room._id, exitSquares[exit]);
|
433 | })
|
434 | })
|
435 | .then(() => {
|
436 | var invaderGoal = Math.floor(C.INVADERS_ENERGY_GOAL * (Math.random()*0.6 + 0.7));
|
437 | if(Math.random() < 0.1) {
|
438 | invaderGoal *= Math.floor( Math.random() > 0.5 ? 2 : 0.5 );
|
439 | }
|
440 | return db.rooms.update({_id: room._id}, {$set: {invaderGoal}})
|
441 | })
|
442 | .then(() => db['rooms.objects'].update({$and: [{room: room._id}, {type: 'source'}]}, {$set: {invaderHarvested: 0}}));
|
443 | })
|
444 | })));
|
445 | }
|
446 |
|
447 | function purgeTransactions() {
|
448 | return db.transactions.find()
|
449 | .then(data => {
|
450 | data = _.sortBy(data, i => -parseInt(i.time));
|
451 |
|
452 | var senders = {}, recipients = {}, forDelete = [];
|
453 | data.forEach(i => {
|
454 | var flag1 = true, flag2 = true;
|
455 | senders[i.sender] = senders[i.sender] || [];
|
456 | if(senders[i.sender].length < 100) {
|
457 | senders[i.sender].push(i);
|
458 | flag1 = false;
|
459 | }
|
460 | recipients[i.recipient] = recipients[i.recipient] || [];
|
461 | if(recipients[i.recipient].length < 100) {
|
462 | recipients[i.recipient].push(i);
|
463 | flag2 = false;
|
464 | }
|
465 | if(flag1 && flag2) {
|
466 | forDelete.push(i._id);
|
467 | }
|
468 | });
|
469 |
|
470 | if(forDelete.length > 0) {
|
471 | return db.transactions.removeWhere({_id: {$in: forDelete}});
|
472 | }
|
473 | })
|
474 | }
|
475 |
|
476 | function calcMarketStats() {
|
477 |
|
478 | };
|