UNPKG

20.3 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};
22
23module.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
33function isBus(coord) {
34 return coord < 0 && (coord+1) % 10 == 0 || coord > 0 && (coord) % 10 == 0 || coord == 0;
35}
36
37function 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
42function 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
124function 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
178function 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
195function genPowerBanks() {
196
197 // TODO
198
199 /*return common.getGametime()
200 .then(gameTime => {
201 return db.rooms.find({$and: [{bus: true}, {status: 'normal'}]})
202 .then(rooms => q.all(rooms.map(room => {
203
204 var respawnTime = Math.round(Math.random()*C.POWER_BANK_RESPAWN_TIME/2 + C.POWER_BANK_RESPAWN_TIME*0.75);
205
206 if (!room.powerBankTime) {
207 room.powerBankTime = gameTime + respawnTime;
208 return db.rooms.update({_id: room._id}, {$set: room});
209 }
210 if (gameTime >= room.powerBankTime) {
211 room.powerBankTime = gameTime + respawnTime;
212 room.active = true;
213
214 return db['rooms.terrain'].findOne({room: room._id})
215 .then((data) => {
216
217 var x, y, isWall, hasExit;
218 do {
219 x = Math.floor(Math.random() * 40 + 5);
220 y = Math.floor(Math.random() * 40 + 5);
221 isWall = parseInt(data.terrain.charAt(y * 50 + x)) & 1;
222 hasExit = false;
223 for (var dx = -1; dx <= 1; dx++) {
224 for (var dy = -1; dy <= 1; dy++) {
225 if (!(parseInt(data.terrain.charAt((y + dy) * 50 + x + dx)) & 1)) {
226 hasExit = true;
227 }
228 }
229 }
230 }
231 while (!isWall || !hasExit);
232
233 var power = Math.floor(Math.random() * (C.POWER_BANK_CAPACITY_MAX - C.POWER_BANK_CAPACITY_MIN) + C.POWER_BANK_CAPACITY_MIN);
234 if(Math.random() < C.POWER_BANK_CAPACITY_CRIT) {
235 power += C.POWER_BANK_CAPACITY_MAX;
236 }
237
238 return db['rooms.objects'].insert({
239 type: 'powerBank',
240 x, y,
241 room: room._id,
242 power,
243 hits: C.POWER_BANK_HITS,
244 hitsMax: C.POWER_BANK_HITS,
245 decayTime: gameTime + C.POWER_BANK_DECAY
246 });
247 })
248 .then(() => db.rooms.update({_id: room._id}, {$set: room}));
249 }
250 })));
251 })*/
252}
253
254function 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
447function 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
476function calcMarketStats() {
477 // TODO
478};