1 | const _ = require('lodash');
|
2 | const common = require('@screeps/common');
|
3 | const config = common.configManager.config;
|
4 | const C = config.common.constants;
|
5 | const db = common.storage.db;
|
6 | const env = common.storage.env;
|
7 | const q = require('q');
|
8 | const png = require('pngjs').PNG;
|
9 | const fs = require('fs');
|
10 | const path = require('path');
|
11 |
|
12 | exports.roomNameFromXY = function(x,y) {
|
13 | if(x < 0) {
|
14 | x = 'W'+(-x-1);
|
15 | }
|
16 | else {
|
17 | x = 'E'+(x);
|
18 | }
|
19 | if(y < 0) {
|
20 | y = 'N'+(-y-1);
|
21 | }
|
22 | else {
|
23 | y = 'S'+(y);
|
24 | }
|
25 | return ""+x+y;
|
26 | }
|
27 |
|
28 | exports.roomNameToXY = function(name) {
|
29 | var [match,hor,x,ver,y] = name.match(/^(\w)(\d+)(\w)(\d+)$/);
|
30 | if(hor == 'W') {
|
31 | x = -x-1;
|
32 | }
|
33 | else {
|
34 | x = +x;
|
35 |
|
36 | }
|
37 | if(ver == 'N') {
|
38 | y = -y-1;
|
39 | }
|
40 | else {
|
41 | y = +y;
|
42 |
|
43 | }
|
44 | return [x,y];
|
45 | }
|
46 |
|
47 | exports.translateModulesFromDb = function(modules) {
|
48 | modules = modules || {};
|
49 |
|
50 | for(var key in modules) {
|
51 | var newKey = key.replace(/\$DOT\$/g, '.');
|
52 | newKey = newKey.replace(/\$SLASH\$/g, '/');
|
53 | newKey = newKey.replace(/\$BACKSLASH\$/g, '\\');
|
54 | if(newKey != key) {
|
55 | modules[newKey] = modules[key];
|
56 | delete modules[key];
|
57 | }
|
58 | }
|
59 | return modules;
|
60 | };
|
61 |
|
62 | exports.translateModulesToDb = function(modules) {
|
63 | modules = modules || {};
|
64 |
|
65 | for(var key in modules) {
|
66 | var newKey = key.replace(/\./g,'$DOT$');
|
67 | newKey = newKey.replace(/\//g,'$SLASH$');
|
68 | newKey = newKey.replace(/\\/g,'$BACKSLASH$');
|
69 |
|
70 | if(newKey[0] == '$') {
|
71 | delete modules[key];
|
72 | continue;
|
73 | }
|
74 |
|
75 | if(newKey != key) {
|
76 | modules[newKey] = modules[key];
|
77 | delete modules[key];
|
78 | }
|
79 | }
|
80 |
|
81 | if(!modules.main) {
|
82 | modules.main = '';
|
83 | }
|
84 |
|
85 | return modules;
|
86 | };
|
87 |
|
88 | exports.getUserWorldStatus = function(user) {
|
89 | return db['rooms.objects'].count({user: ""+user._id})
|
90 | .then((objectsCnt) => {
|
91 | if(!objectsCnt) {
|
92 | return {status: 'empty'};
|
93 | }
|
94 | return db['rooms.objects'].find({$and: [
|
95 | {user: ""+user._id},
|
96 | {type: {$in: ['spawn','controller']}}
|
97 | ]}).then((objects) => {
|
98 | var spawns = false;
|
99 | if(objects) {
|
100 | objects.forEach((i) => {
|
101 | if (i.type == 'spawn') {
|
102 | if(!_.any(objects, {type: 'controller', room: i.room, user: i.user})) {
|
103 | return;
|
104 | }
|
105 | spawns = true;
|
106 | }
|
107 | })
|
108 | }
|
109 | return {status: spawns ? 'normal' : 'lost'};
|
110 | })
|
111 | })
|
112 | };
|
113 |
|
114 |
|
115 | exports.respawnUser = async function(userId) {
|
116 | return db['users'].findOne({username: C.SYSTEM_USERNAME})
|
117 | .then(async systemUser => {
|
118 | if(!systemUser) {
|
119 | return q.reject('no system user');
|
120 | }
|
121 | const gameTime = await common.getGametime();
|
122 | await db['rooms.objects'].removeWhere({$and: [{user: ""+userId}, {type: {$in: ['creep','powerCreep','constructionSite']}}]});
|
123 | await db['users.power_creeps'].removeWhere({user: ""+userId});
|
124 | const objects = await db['rooms.objects'].find({user: "" + userId, type: {$in: _.keys(C.CONSTRUCTION_COST)}});
|
125 | if(objects.length) {
|
126 | await db['rooms.objects'].insert(objects.map(i => ({
|
127 | type: 'ruin',
|
128 | user: ""+systemUser._id,
|
129 | room: i.room,
|
130 | x: i.x,
|
131 | y: i.y,
|
132 | structure: {
|
133 | id: i._id,
|
134 | type: i.type,
|
135 | hits: 0,
|
136 | hitsMax: i.hitsMax,
|
137 | user: ""+systemUser._id
|
138 | },
|
139 | store: i.store || {},
|
140 | destroyTime: gameTime,
|
141 | decayTime: gameTime + 500000
|
142 | })));
|
143 | }
|
144 | await db['rooms.objects'].update({user: "" + userId, type: 'ruin'}, {$set: {user: ""+systemUser._id}});
|
145 | await db['rooms.objects'].removeWhere({user: "" + userId, type: {$ne: 'controller'}});
|
146 | await db['rooms.flags'].removeWhere({user: ""+userId});
|
147 | const controllers = db['rooms.objects'].find({$and: [{user: ""+userId}, {type: 'controller'}]});
|
148 | for(let i in controllers) {
|
149 | await db.rooms.update({_id: controllers[i].room}, {$set: {status: 'normal'}});
|
150 | }
|
151 | await db['rooms.objects'].update({$and: [{user: "" + userId}, {type: 'controller'}]}, {
|
152 | $set: {
|
153 | level: 0,
|
154 | hits: 0,
|
155 | hitsMax: 0,
|
156 | progress: 0,
|
157 | progressTotal: 0,
|
158 | user: null,
|
159 | downgradeTime: null,
|
160 | safeMode: null,
|
161 | safeModeAvailable: 0,
|
162 | safeModeCooldown: null
|
163 | }
|
164 | });
|
165 | })
|
166 | .then(() => db['users'].update({_id: "" + userId}, {$set: {rooms: []}}));
|
167 | };
|
168 |
|
169 | exports.withHelp = function(array) {
|
170 | var fn = array[1];
|
171 | fn._help = array[0];
|
172 | return fn;
|
173 | };
|
174 |
|
175 | exports.generateCliHelp = function(prefix, container) {
|
176 | return `Available methods:\r\n`+Object.keys(container).filter(i => typeof container[i] == 'function').map(i => ' - ' + prefix + (container[i]._help || i)).join('\r\n');
|
177 | };
|
178 |
|
179 | exports.writePng = function(colors, width, height, filename) {
|
180 |
|
181 | var image = new png({width, height});
|
182 |
|
183 | for(var y=0; y<height; y++) {
|
184 | for(var x=0; x<width; x++) {
|
185 | var idx = (width*y + x) << 2;
|
186 |
|
187 | image.data[idx] = colors[y][x][0];
|
188 | image.data[idx+1] = colors[y][x][1];
|
189 | image.data[idx+2] = colors[y][x][2];
|
190 | image.data[idx+3] = colors[y][x][3] === undefined ? 255 : colors[y][x][3];
|
191 | }
|
192 | }
|
193 |
|
194 | var defer = q.defer();
|
195 | image.pack().pipe(fs.createWriteStream(filename)).on('finish', () => defer.resolve());
|
196 | return defer.promise;
|
197 | };
|
198 |
|
199 | exports.createTerrainColorsMap = function(terrain, zoomIn) {
|
200 | var colors = {},
|
201 | width = 50, height = 50;
|
202 |
|
203 | for(var y=0; y<height; y++) {
|
204 | if(zoomIn) {
|
205 | colors[y * 3] = {};
|
206 | colors[y * 3 + 1] = {};
|
207 | colors[y * 3 + 2] = {};
|
208 | }
|
209 | else {
|
210 | colors[y] = {};
|
211 | }
|
212 | for(var x=0; x<width; x++) {
|
213 |
|
214 | var color;
|
215 | if(common.checkTerrain(terrain,x,y,C.TERRAIN_MASK_WALL)) {
|
216 | color = [0,0,0];
|
217 | }
|
218 | else if(common.checkTerrain(terrain,x,y,C.TERRAIN_MASK_SWAMP)) {
|
219 | color = [35,37,19];
|
220 | }
|
221 | else if(x == 0 || y == 0 || x == 49 || y == 49) {
|
222 | color = [50,50,50];
|
223 | }
|
224 | else {
|
225 | color = [43,43,43];
|
226 | }
|
227 | if(zoomIn) {
|
228 | for (var dx = 0; dx < 3; dx++) {
|
229 | for (var dy = 0; dy < 3; dy++) {
|
230 | colors[y * 3 + dy][x * 3 + dx] = color;
|
231 | }
|
232 | }
|
233 | }
|
234 | else {
|
235 | colors[y][x] = color;
|
236 | }
|
237 | }
|
238 | }
|
239 |
|
240 | return colors;
|
241 | };
|
242 |
|
243 | exports.writeTerrainToPng = function(terrain, filename, zoomIn) {
|
244 |
|
245 | var colors = exports.createTerrainColorsMap(terrain, zoomIn);
|
246 | return exports.writePng(colors, 50*(zoomIn?3:1), 50*(zoomIn?3:1), filename);
|
247 | };
|
248 |
|
249 | exports.loadBot = function(name) {
|
250 | var dir = config.common.bots[name];
|
251 | if(!dir) {
|
252 | throw new Error(`Bot AI with the name "${name}" doesn't exist`);
|
253 | }
|
254 | var stat = fs.statSync(dir);
|
255 | if (!stat.isDirectory()) {
|
256 | throw new Error(`"${dir}" is not a directory`);
|
257 | }
|
258 | fs.statSync(path.resolve(dir, 'main.js'));
|
259 | var files = fs.readdirSync(dir), modules = {};
|
260 | files.forEach(file => {
|
261 | var m = file.match(/^(.*)\.js$/);
|
262 | if(!m) {
|
263 | return;
|
264 | }
|
265 | modules[m[1]] = fs.readFileSync(path.resolve(dir, file), {encoding: 'utf8'});
|
266 | });
|
267 | return exports.translateModulesToDb(modules);
|
268 | };
|
269 |
|
270 | exports.reloadBotUsers = function(name) {
|
271 |
|
272 | return db.users.find({bot: name})
|
273 | .then(users => {
|
274 | if(!users.length) {
|
275 | return 'No bot players found';
|
276 | }
|
277 | var modules = exports.loadBot(name);
|
278 | var timestamp = Date.now();
|
279 |
|
280 | return db['users.code'].insert(users.map(i => ({
|
281 | user: i._id,
|
282 | branch: 't'+timestamp,
|
283 | timestamp,
|
284 | activeWorld: true,
|
285 | activeSim: true,
|
286 | modules
|
287 | })))
|
288 | .then(() => db['users.code'].removeWhere({$and: [
|
289 | {user: {$in: users.map(i => i._id)}},
|
290 | {branch: {$ne: 't'+timestamp}}
|
291 | ]}))
|
292 | .then(() => 'Reloaded scripts for users: '+users.map(i => i.username).join(', '));
|
293 | })
|
294 | };
|
295 |
|
296 | exports.isBus = function isBus(coord) {
|
297 | return coord < 0 && (coord+1) % 10 == 0 || coord > 0 && (coord) % 10 == 0 || coord == 0;
|
298 | };
|
299 |
|
300 | exports.isCenter = function isCenter(x,y) {
|
301 | 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) &&
|
302 | (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);
|
303 | };
|
304 |
|
305 | exports.isVeryCenter = function isVeryCenter(x,y) {
|
306 | return (x < 0 && Math.abs(x+1)%10 == 5 || x >= 0 && Math.abs(x)%10 == 5) &&
|
307 | (y < 0 && Math.abs(y+1)%10 == 5 || y >= 0 && Math.abs(y)%10 == 5);
|
308 | };
|
309 |
|
310 | exports.activateRoom = function activateRoom(room) {
|
311 | return env.sadd(env.keys.ACTIVE_ROOMS, room);
|
312 | };
|
313 |
|
314 | exports.getActiveRooms = function getActiveRooms() {
|
315 | return env.smembers(env.keys.ACTIVE_ROOMS);
|
316 | };
|
317 |
|
318 | exports.findFreePos = function findFreePos(roomName, distance, rect, exclude) {
|
319 | if(!rect) {
|
320 | rect = {x1: 4, x2: 45, y1: 4, y2: 45};
|
321 | }
|
322 |
|
323 | return q.all([
|
324 | db['rooms.objects'].find({room: roomName}),
|
325 | db['rooms.terrain'].findOne({room: roomName})])
|
326 | .then(([objects, terrain]) => {
|
327 | if (!terrain) {
|
328 | return q.reject();
|
329 | }
|
330 | var x, y, spot, hasObjects, counter = 0;
|
331 | do {
|
332 |
|
333 | x = rect.x1 + Math.floor(Math.random() * (rect.x2 - rect.x1));
|
334 | y = rect.y1 + Math.floor(Math.random() * (rect.y2 - rect.y1));
|
335 | if(exclude && exclude.x == x && exclude.y == y) {
|
336 | continue;
|
337 | }
|
338 | spot = true;
|
339 | for (var dx = -distance; dx <= distance; dx++) {
|
340 | for (var dy = -distance; dy <= distance; dy++) {
|
341 | if (common.checkTerrain(terrain.terrain, x + dx, y + dy, C.TERRAIN_MASK_WALL)) {
|
342 | spot = false;
|
343 | }
|
344 | }
|
345 | }
|
346 | hasObjects = _.any(objects, i => Math.abs(i.x - x) <= distance && Math.abs(i.y - y) <= distance &&
|
347 | C.OBSTACLE_OBJECT_TYPES.concat(['rampart','portal']).indexOf(i.type) != -1);
|
348 | counter++;
|
349 | }
|
350 | while ((!spot || hasObjects) && counter < 500);
|
351 |
|
352 | if (!spot || hasObjects) {
|
353 | return q.reject();
|
354 | }
|
355 |
|
356 | return {x, y};
|
357 | });
|
358 | }
|