UNPKG

21.5 kBJavaScriptView Raw
1var 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
13config.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 deletePowerCreeps: [10*60, deletePowerCreeps]
22};
23
24module.exports.run = function() {
25 _.forEach(config.cronjobs, (i,key) => {
26 if(Date.now() - (i[2]||0) > i[0]*1000) {
27 console.log(`Running cronjob '${key}'`);
28 i[2] = Date.now();
29 i[1]();
30 }
31 });
32};
33
34function isBus(coord) {
35 return coord < 0 && (coord+1) % 10 == 0 || coord > 0 && (coord) % 10 == 0 || coord == 0;
36}
37
38function isCenter(x,y) {
39 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) &&
40 (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);
41}
42
43function recreateNpcOrders() {
44 var gameTime;
45
46 var sellMinerals = ['X','Z','K','L','U','O','O','H','H','Z','K','L','U','O','O','H','H'];
47 var buyMinerals = ['X','Z','K','L','U','O','O','H','H','Z','K','L','U','O','O','H','H'];
48 var sellPrice = {
49 H: 3000,
50 O: 3000,
51 Z: 6000,
52 K: 6000,
53 U: 6000,
54 L: 6000,
55 X: 18000
56 };
57 var buyPrice = 1000;
58
59 var sellMineralAmount = 40, sellEnergyAmount = 40, buyMineralAmount = 20, period = 5000;
60
61 var cnt = 0;
62
63 return common.getGametime()
64 .then(data => gameTime = data)
65 .then(() => db['rooms.objects'].find({$and: [{type: 'terminal'}, {user: {$eq: null}}]}))
66 .then(terminals => common.qSequence(terminals, terminal => {
67 return db.rooms.findOne({_id: terminal.room})
68 .then(room => {
69 if(room.status != 'normal') {
70 return;
71 }
72 if(room.nextNpcMarketOrder && room.nextNpcMarketOrder > gameTime) {
73 return;
74 }
75 const nowTimestamp = new Date().getTime();
76 var sellMineral = sellMinerals[Math.floor(Math.random()*sellMinerals.length)];
77 var buyMineral = buyMinerals[Math.floor(Math.random()*buyMinerals.length)];
78 var orders = [];
79
80 orders.push({
81 created: gameTime,
82 createdTimestamp: nowTimestamp,
83 active: true,
84 type: 'sell',
85 amount: period*sellMineralAmount,
86 remainingAmount: period*sellMineralAmount,
87 totalAmount: period*sellMineralAmount,
88 resourceType: sellMineral,
89 price: sellPrice[sellMineral],
90 roomName: terminal.room
91 });
92
93 if(Math.random() < 0.5) {
94 orders.push({
95 created: gameTime,
96 createdTimestamp: nowTimestamp,
97 active: true,
98 type: 'sell',
99 amount: period*sellEnergyAmount,
100 remainingAmount: period*sellEnergyAmount,
101 totalAmount: period*sellEnergyAmount,
102 resourceType: 'energy',
103 price: 1000,
104 roomName: terminal.room
105 });
106 }
107 if(Math.random() < 0.25) {
108 orders.push({
109 created: gameTime,
110 createdTimestamp: nowTimestamp,
111 active: true,
112 type: 'buy',
113 amount: period*buyMineralAmount,
114 remainingAmount: period*buyMineralAmount,
115 totalAmount: period*buyMineralAmount,
116 resourceType: buyMineral,
117 price: buyPrice,
118 roomName: terminal.room
119 });
120 }
121 cnt++;
122 return db['market.orders'].removeWhere({roomName: room._id})
123 .then(() => db['market.orders'].insert(orders))
124 .then(() => db.rooms.update({_id: room._id}, {$set: {nextNpcMarketOrder: gameTime + Math.round(period*(0.8 + 0.4*Math.random()))}}));
125 })
126 }))
127}
128
129function sendNotifications() {
130
131 var notifications, userIds;
132 var filterDate = new Date();
133 return db['users.notifications'].find({date: {$lt: filterDate.getTime()}})
134 .then((data) => {
135 notifications = data;
136 userIds = _(notifications).pluck('user').uniq(false, (i) => i.toString()).value();
137 })
138 .then(() => db.users.find({_id: {$in: userIds}}))
139 .then((users) => {
140 var promise = q.when();
141 users.forEach((user) => {
142
143 var notificationIdsToRemove = [];
144
145 promise = promise.then(() => {
146
147 var userNotifications = _.filter(notifications, (i) => i.user == user._id);
148
149 if (user.notifyPrefs && (user.notifyPrefs.disabled || !user.email)) {
150 userNotifications.forEach((notification) => {
151 notificationIdsToRemove.push(notification._id);
152 });
153 return;
154 }
155
156 var interval = 5;
157 if (user.notifyPrefs && user.notifyPrefs.interval > 5) {
158 interval = user.notifyPrefs.interval;
159 }
160 interval *= 60 * 1000;
161
162 if (user.lastNotifyDate && (user.lastNotifyDate + interval > Date.now())) {
163 return;
164 }
165
166 userNotifications.forEach((notification) => {
167 notificationIdsToRemove.push(notification._id);
168 });
169
170 config.backend.emit('sendUserNotifications',user,
171 userNotifications.map(i => _.pick(i, ['message','date','count','type'])));
172 })
173 .catch((e) => console.log(`Error sending a message to ${user.username}: ${e}`))
174 .then(() => notificationIdsToRemove.length > 0 && q.all([
175 db['users.notifications'].removeWhere({_id: {$in: notificationIdsToRemove}}),
176 db.users.update({_id: user._id}, {$set: {lastNotifyDate: Date.now()}})
177 ]))
178 });
179 return promise;
180 })
181}
182
183function roomsForceUpdate() {
184 return common.getGametime()
185 .then(gameTime => {
186 return db.rooms.find({$and: [{status: {$ne: 'out of borders'}}, {active: false}]})
187 .then(rooms => common.qSequence(rooms, room => {
188 if (!room.nextForceUpdateTime || gameTime >= room.nextForceUpdateTime) {
189 return db.rooms.update({_id: room._id}, {
190 $set: {
191 active: true,
192 nextForceUpdateTime: gameTime + 90 + Math.floor(Math.random() * 20)
193 }
194 });
195 }
196 }))
197 });
198}
199
200function genPowerBanks() {
201
202 // TODO
203
204 /*return common.getGametime()
205 .then(gameTime => {
206 return db.rooms.find({$and: [{bus: true}, {status: 'normal'}]})
207 .then(rooms => q.all(rooms.map(room => {
208
209 var respawnTime = Math.round(Math.random()*C.POWER_BANK_RESPAWN_TIME/2 + C.POWER_BANK_RESPAWN_TIME*0.75);
210
211 if (!room.powerBankTime) {
212 room.powerBankTime = gameTime + respawnTime;
213 return db.rooms.update({_id: room._id}, {$set: room});
214 }
215 if (gameTime >= room.powerBankTime) {
216 room.powerBankTime = gameTime + respawnTime;
217 room.active = true;
218
219 return db['rooms.terrain'].findOne({room: room._id})
220 .then((data) => {
221
222 var x, y, isWall, hasExit;
223 do {
224 x = Math.floor(Math.random() * 40 + 5);
225 y = Math.floor(Math.random() * 40 + 5);
226 isWall = parseInt(data.terrain.charAt(y * 50 + x)) & 1;
227 hasExit = false;
228 for (var dx = -1; dx <= 1; dx++) {
229 for (var dy = -1; dy <= 1; dy++) {
230 if (!(parseInt(data.terrain.charAt((y + dy) * 50 + x + dx)) & 1)) {
231 hasExit = true;
232 }
233 }
234 }
235 }
236 while (!isWall || !hasExit);
237
238 var power = Math.floor(Math.random() * (C.POWER_BANK_CAPACITY_MAX - C.POWER_BANK_CAPACITY_MIN) + C.POWER_BANK_CAPACITY_MIN);
239 if(Math.random() < C.POWER_BANK_CAPACITY_CRIT) {
240 power += C.POWER_BANK_CAPACITY_MAX;
241 }
242
243 return db['rooms.objects'].insert({
244 type: 'powerBank',
245 x, y,
246 room: room._id,
247 power,
248 hits: C.POWER_BANK_HITS,
249 hitsMax: C.POWER_BANK_HITS,
250 decayTime: gameTime + C.POWER_BANK_DECAY
251 });
252 })
253 .then(() => db.rooms.update({_id: room._id}, {$set: room}));
254 }
255 })));
256 })*/
257}
258
259function genInvaders() {
260
261 function checkExit(roomName, exit) {
262 var [x,y] = utils.roomNameToXY(roomName);
263 if(exit == 'top') y--;
264 if(exit == 'right') x++;
265 if(exit == 'bottom') y++;
266 if(exit == 'left') x--;
267 var newRoomName = utils.roomNameFromXY(x,y);
268 return db['rooms.objects'].findOne({$and: [{room: newRoomName}, {type: 'controller'}]})
269 .then(controller => {
270 if(controller && (controller.user || controller.reservation)) {
271 return q.reject();
272 }
273 })
274 }
275
276 function createCreep(type, room, square, boosted) {
277
278 var [x,y] = utils.roomNameToXY(room);
279
280 var body = {
281 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'],
282 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'],
283 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'],
284 smallHealer: ['move','move','move','move','heal','heal','heal','heal','heal','move'],
285 smallRanged: ['tough','tough','move','move','move','move','ranged_attack','ranged_attack','ranged_attack','move'],
286 smallMelee: ['tough','tough','move','move','move','move','ranged_attack','work','attack','move']
287 };
288
289 var creep = {
290 type: 'creep',
291 user: '2',
292 body: _.map(body[type], type => ({type, hits: 100})),
293 hits: body[type].length * 100,
294 hitsMax: body[type].length * 100,
295 ticksToLive: 1500,
296 x: square[0],
297 y: square[1],
298 room,
299 fatigue: 0,
300 energy: 0,
301 energyCapacity: 0,
302 name: `invader_${room}_${Math.floor(Math.random()*1000)}`
303 };
304
305 if(boosted) {
306 creep.body.forEach(i => {
307 if(i.type == 'heal') {
308 i.boost = isCenter(x,y) ? 'XLHO2' : 'LO';
309 }
310 if(i.type == 'ranged_attack') {
311 i.boost = isCenter(x,y) ? 'XKHO2' : 'KO';
312 }
313 if(i.type == 'work') {
314 i.boost = isCenter(x,y) ? 'XZH2O' : 'ZH';
315 }
316 if(i.type == 'attack') {
317 i.boost = isCenter(x,y) ? 'XUH2O' : 'UH';
318 }
319 if(i.type == 'tough') {
320 i.boost = isCenter(x,y) ? 'XGHO2' : 'GO';
321 }
322 })
323 }
324
325 return db['rooms.objects'].insert(creep);
326 }
327
328 function findClosestExit(square, exits) {
329 var sortedExits = _.sortBy(exits, i => Math.max(Math.abs(i[0] - square[0]), Math.abs(i[1] - square[1])));
330 return sortedExits[0];
331 }
332
333
334 function createRaid(controllerLevel, room, exits) {
335
336 var type = controllerLevel && controllerLevel >= 4 ? 'big' : 'small';
337
338 var max = 1, count = 1, boostChance = 0.5;
339
340 var [x,y] = utils.roomNameToXY(room);
341
342 if(Math.random() > 0.9 || isCenter(x,y)) {
343 max = 2;
344 boostChance = type == 'big' ? 0 : 0.5;
345 if (Math.random() > 0.8 || isCenter(x,y)) {
346 max = 5;
347 if (type == 'big') {
348 if (controllerLevel < 5) {
349 max = 2;
350 }
351 else if (controllerLevel < 6) {
352 max = 2;
353 }
354 else if (controllerLevel < 7) {
355 max = 3;
356 }
357 else if (controllerLevel < 8) {
358 boostChance = 0.4;
359 max = 3;
360 }
361 else {
362 boostChance = 0.4;
363 max = 5;
364 }
365 }
366 }
367
368 count = Math.floor(Math.random()*(max-1)) + 2;
369 count = Math.min(count, exits.length);
370 }
371
372 var promises = [], lastSquare;
373
374 for(var i=0; i<count; i++) {
375 var subtype = i == 0 && !isCenter(x,y) ? 'Melee' :
376 i == 0 || (i == 1 || i == 2 && count == 5) && Math.random() > 0.5 ? 'Ranged' :
377 'Healer';
378
379 var square = lastSquare ? findClosestExit(lastSquare, exits) : exits[Math.floor(Math.random() * exits.length)];
380 if(!square) {
381 break;
382 }
383 promises.push(createCreep(type + subtype, room, square, Math.random() < boostChance));
384 _.pull(exits, square);
385 lastSquare = square;
386 }
387
388 return q.all(promises);
389 }
390
391 return db.rooms.find({$and: [{status: 'normal'}, {active: true}]})
392 .then(rooms => q.all(rooms.map(room => {
393
394 return db['rooms.objects'].find({$and: [{room: room._id}, {type: {$in: ['source','controller','creep']}}]})
395 .then(objects => {
396 var sources = _.filter(objects, {type: 'source'});
397 var creeps = _.filter(objects, {type: 'creep', user: '2'});
398 if(creeps.length) {
399 return;
400 }
401 var invaderHarvested = _.sum(sources, 'invaderHarvested');
402 var goal = room.invaderGoal || C.INVADERS_ENERGY_GOAL;
403 if(goal != 1 && invaderHarvested < goal) {
404 return;
405 }
406 return db['rooms.terrain'].findOne({room: room._id})
407 .then(terrain => {
408 var exits = {}, exitSquares = {top: [], left: [], right: [], bottom: []};
409 for(var i=0; i<49; i++) {
410 if(!common.checkTerrain(terrain.terrain, i, 0, C.TERRAIN_MASK_WALL)) {
411 exits.top = true;
412 exitSquares.top.push([i,0]);
413 }
414 if(!common.checkTerrain(terrain.terrain, i, 49, C.TERRAIN_MASK_WALL)) {
415 exits.bottom = true;
416 exitSquares.bottom.push([i,49]);
417 }
418 if(!common.checkTerrain(terrain.terrain, 0, i, C.TERRAIN_MASK_WALL)) {
419 exits.left = true;
420 exitSquares.left.push([0,i]);
421 }
422 if(!common.checkTerrain(terrain.terrain, 49, i, C.TERRAIN_MASK_WALL)) {
423 exits.right = true;
424 exitSquares.right.push([49,i]);
425 }
426 }
427 exits = _.keys(exits);
428 return q.all(_.map(exits, exit => {
429 return checkExit(room._id, exit).catch(() => _.pull(exits, exit));
430 }))
431 .then(() => {
432 if(!exits.length) {
433 return;
434 }
435 var exit = exits[Math.floor(Math.random()*exits.length)];
436 var controller = _.find(objects, {type: 'controller'});
437 return createRaid(controller && controller.user && controller.level, room._id, exitSquares[exit]);
438 })
439 })
440 .then(() => {
441 var invaderGoal = Math.floor(C.INVADERS_ENERGY_GOAL * (Math.random()*0.6 + 0.7));
442 if(Math.random() < 0.1) {
443 invaderGoal *= Math.floor( Math.random() > 0.5 ? 2 : 0.5 );
444 }
445 return db.rooms.update({_id: room._id}, {$set: {invaderGoal}})
446 })
447 .then(() => db['rooms.objects'].update({$and: [{room: room._id}, {type: 'source'}]}, {$set: {invaderHarvested: 0}}));
448 })
449 })));
450}
451
452function purgeTransactions() {
453 return db.transactions.find()
454 .then(data => {
455 data = _.sortBy(data, i => -parseInt(i.time));
456
457 var senders = {}, recipients = {}, forDelete = [];
458 data.forEach(i => {
459 var flag1 = true, flag2 = true;
460 senders[i.sender] = senders[i.sender] || [];
461 if(senders[i.sender].length < 100) {
462 senders[i.sender].push(i);
463 flag1 = false;
464 }
465 recipients[i.recipient] = recipients[i.recipient] || [];
466 if(recipients[i.recipient].length < 100) {
467 recipients[i.recipient].push(i);
468 flag2 = false;
469 }
470 if(flag1 && flag2) {
471 forDelete.push(i._id);
472 }
473 });
474
475 if(forDelete.length > 0) {
476 return db.transactions.removeWhere({_id: {$in: forDelete}});
477 }
478 })
479}
480
481function calcMarketStats() {
482 // TODO
483};
484
485function calcPowerLevelBase(level) {
486 return Math.pow(level, C.POWER_LEVEL_POW) * C.POWER_LEVEL_MULTIPLY;
487}
488
489function calcPowerLevel(power) {
490 return Math.floor( Math.pow( (power || 0) / C.POWER_LEVEL_MULTIPLY, 1 / C.POWER_LEVEL_POW ) );
491}
492
493function deletePowerCreeps() {
494 return db['users.power_creeps'].find({deleteTime: {$lt: Date.now()}})
495 .then((data) => _.reduce(data, (promise, creep) => {
496 if(!creep.deleteTime) {
497 return promise;
498 }
499 return promise
500 .then(() => db['users'].findOne({_id: creep.user}))
501 .then(user => {
502 var level = calcPowerLevel(user.power);
503 var basePrev = calcPowerLevelBase(level-1);
504 var baseCurrent = calcPowerLevelBase(level);
505 var baseNext = calcPowerLevelBase(level+1);
506 var change = Math.round(user.power - basePrev -
507 (user.power - baseCurrent) * (baseCurrent - basePrev) / (baseNext - baseCurrent));
508 return q.all([
509 db['users'].update({_id: user._id}, {$inc: {power: -change}}),
510 db['users.power_creeps'].removeWhere({_id: creep._id})
511 ]);
512 })
513 }, q.when()));
514}