1 /*
2  * Copyright (c) 2017-2018 sel-project
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to deal
6  * in the Software without restriction, including without limitation the rights
7  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8  * copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in all
12  * copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20  * SOFTWARE.
21  *
22  */
23 /**
24  * Copyright: 2017-2018 sel-project
25  * License: MIT
26  * Authors: Kripth
27  * Source: $(HTTP github.com/sel-project/selery/source/selery/node/server.d, selery/node/server.d)
28  */
29 module selery.node.server;
30 
31 import core.atomic : atomicOp;
32 import core.thread : getpid, Thread;
33 
34 import std.algorithm : canFind, min;
35 import std.bitmanip : nativeToBigEndian;
36 static import std.concurrency;
37 import std.conv : to;
38 import std.datetime : dur, Duration;
39 import std.datetime.stopwatch : StopWatch;
40 static import std.file;
41 import std.json : JSON_TYPE, JSONValue, parseJSON;
42 import std.process : executeShell;
43 import std.socket : SocketException, Address;
44 import std.string; //TODO selective imports
45 import std.traits : Parameters;
46 import std.uuid : UUID;
47 
48 import arsd.terminal : Terminal, ConsoleOutputType;
49 
50 import imageformats.png : read_png_from_mem;
51 
52 import resusage.memory;
53 import resusage.cpu;
54 
55 import sel.hncom.about;
56 import sel.hncom.handler : HncomHandler;
57 import sel.server.bedrock : bedrockSupportedProtocols;
58 
59 import selery.world.world : World; // do not move this import down
60 
61 import selery.about;
62 import selery.command.command : Command, CommandSender;
63 import selery.command.execute : executeCommand;
64 import selery.commands : Commands;
65 import selery.config : Config, Difficulty, Gamemode;
66 import selery.entity.human : Skin;
67 import selery.event.event : Event, EventListener;
68 import selery.event.node;
69 import selery.event.world.world : WorldEvent;
70 import selery.lang : LanguageManager, Translation;
71 import selery.log : Format, Message, Logger;
72 import selery.node.handler; //TODO selective imports
73 import selery.node.info : PlayerInfo, WorldInfo;
74 import selery.node.node : Node;
75 import selery.player.bedrock : BedrockPlayer, BedrockPlayerImpl;
76 import selery.player.java : JavaPlayer;
77 import selery.player.player : PermissionLevel;
78 import selery.plugin : Plugin, Description;
79 import selery.server : Server;
80 import selery.util.resourcepack : createResourcePacks, serveResourcePacks;
81 import selery.util.tuple : Tuple;
82 import selery.util.util : milliseconds, microseconds;
83 import selery.world.thread;
84 
85 import HncomLogin = sel.hncom.login;
86 import HncomUtil = sel.hncom.util;
87 import HncomStatus = sel.hncom.status;
88 import HncomPlayer = sel.hncom.player;
89 
90 // the signal could be handled on another thread!
91 private shared bool running = true;
92 private shared bool stoppedWithSignal = false;
93 
94 private shared std.concurrency.Tid server_tid;
95 
96 public nothrow @property @safe @nogc bool isServerRunning() {
97 	return running;
98 }
99 
100 version(Windows) {
101 
102 	import core.sys.windows.wincon : CTRL_C_EVENT;
103 	import core.sys.windows.windef : DWORD, BOOL;
104 
105 	alias extern (Windows) BOOL function(DWORD) PHANDLER_ROUTINE;
106 	extern (Windows) BOOL SetConsoleCtrlHandler(PHANDLER_ROUTINE, BOOL);
107 
108 	extern (Windows) int sigHandler(uint sig) {
109 		if(sig == CTRL_C_EVENT) {
110 			std.concurrency.send(cast()server_tid, Stop());
111 			return true; // this will let the process run in background until it kills himself
112 		}
113 		return false; // windows will instantly kill the process
114 	}
115 
116 } else version(Posix) {
117 
118 	import core.sys.posix.signal;
119 
120 	extern (C) void extsig(int sig) {
121 		std.concurrency.send(cast()server_tid, Stop());
122 	}
123 
124 }
125 
126 private struct Stop {}
127 
128 /**
129  * Singleton for the server instance.
130  */
131 final class NodeServer : EventListener!NodeServerEvent, Server, HncomHandler!clientbound {
132 
133 	public immutable bool lite;
134 
135 	private shared ulong start_time;
136 
137 	private Handler handler;
138 	private Address n_hub_address;
139 
140 	private const string[] n_args;
141 
142 	private shared ulong n_id;
143 	private shared ulong uuid_count;
144 
145 	private shared uint n_hub_latency;
146 
147 	public shared std.concurrency.Tid tid; //TODO make private
148 
149 	private shared Config _config;
150 	private shared Logger _logger;
151 	private shared ServerLogger serverlogger;
152 
153 	private shared size_t n_online;
154 	private shared size_t n_max;
155 
156 	private shared Node[uint] nodes_hubid;
157 	private shared Node[string] nodes_names;
158 
159 	private shared Tuple!(string, "website", string, "facebook", string, "twitter", string, "youtube", string, "instagram", string, "googlePlus") n_social;
160 
161 	private shared(uint) _world_count = 0;
162 	private shared(uint) _default_world_id = 0; // 0 = no default world
163 	private shared(WorldInfo)[uint] _worlds;
164 	private shared(WorldInfo)[string] _worlds_names;
165 
166 	private shared(PlayerInfo)[uint] _players;
167 
168 	private shared Plugin[] n_plugins;
169 
170 	public shared EventListener!WorldEvent globalListener;
171 
172 	private shared Command[string] _commands;
173 
174 	public shared this(Address hub, Config config, Plugin[] plugins=[], string[] args=[]) {
175 
176 		assert(config.node !is null);
177 
178 		debug Thread.getThis().name = "node";
179 
180 		this.lite = cast(TidAddress)hub !is null;
181 
182 		this.n_plugins = cast(shared)plugins;
183 
184 		this.n_args = cast(shared)args;
185 
186 		this.tid = server_tid = cast(shared)std.concurrency.thisTid;
187 		
188 		this.n_hub_address = cast(shared)hub;
189 
190 		if(config.hub is null) config.hub = config..new Config.Hub();
191 
192 		this._config = cast(shared)config;
193 
194 		Terminal terminal = Terminal(ConsoleOutputType.linear);
195 		this._logger = cast(shared)new Logger(&terminal, config.lang); // only writes in the console
196 
197 		if(lite) {
198 
199 			this.handler = new shared MessagePassingHandler(cast(shared TidAddress)hub);
200 			this.handleInfoImpl(cast()std.concurrency.receiveOnly!(shared HncomLogin.HubInfo)());
201 
202 		} else {
203 
204 			this.logger.log(Translation("startup.connecting", [to!string(hub), config.node.name]));
205 
206 			try {
207 				this.handler = new shared SocketHandler(hub);
208 				this.handler.send(HncomLogin.ConnectionRequest(config.node.name, config.node.password, config.node.main).encode());
209 			} catch(SocketException e) {
210 				this.logger.logError(Translation("warning.connectionError", [to!string(hub), e.msg]));
211 				return;
212 			}
213 
214 			// remove variables in config that plugins should not read
215 			config.node.password = "";
216 			config.node.ip = "";
217 			config.node.port = ushort(0);
218 
219 			// wait for ConnectionResponse
220 			ubyte[] buffer = this.handler.receive();
221 			if(buffer.length && buffer[0] == HncomLogin.ConnectionResponse.ID) {
222 				auto response = HncomLogin.ConnectionResponse.fromBuffer(buffer[1..$]);
223 				if(response.status == HncomLogin.ConnectionResponse.OK) {
224 					this.handleInfo();
225 				} else {
226 					immutable reason = (){
227 						switch(response.status) with(HncomLogin.ConnectionResponse) {
228 							case OUTDATED_HUB: return "outdatedHub";
229 							case OUTDATED_NODE: return "outdatedNode";
230 							case PASSWORD_REQUIRED: return "passwordRequired";
231 							case WRONG_PASSWORD: return "wrongPassword";
232 							case INVALID_NAME_LENGTH: return "invalidNameLength";
233 							case INVALID_NAME_CHARACTERS: return "invalidNameCharacters";
234 							case NAME_ALREADY_USED: return "nameAlreadyUsed";
235 							case NAME_RESERVED: return "nameReserved";
236 							default: return "unknown";
237 						}
238 					}();
239 					this.logger.logError(Translation("status." ~ reason));
240 					if(response.status == HncomLogin.ConnectionResponse.OUTDATED_HUB || response.status == HncomLogin.ConnectionResponse.OUTDATED_NODE) {
241 						this.logger.logError(Translation("warning.protocolRequired", [to!string(__PROTOCOL__), to!string(response.protocol)]));
242 					}
243 				}
244 			} else {
245 				this.logger.logError(Translation("warning.refused"));
246 			}
247 
248 			this.handler.close();
249 
250 		}
251 		
252 	}
253 
254 	private shared void handleInfo() {
255 
256 		ubyte[] buffer = this.handler.receive();
257 		if(buffer.length && buffer[0] == HncomLogin.HubInfo.ID) {
258 			this.handleInfoImpl(HncomLogin.HubInfo.fromBuffer(buffer[1..$]));
259 		} else {
260 			this.logger.logError(Translation("warning.closed"));
261 		}
262 
263 	}
264 
265 	private shared void handleInfoImpl(HncomLogin.HubInfo info) {
266 
267 		Config config = cast()this._config;
268 
269 		this.n_id = info.serverId;
270 		this.uuid_count = info.reservedUUIDs;
271 
272 		if(info.additionalJSON.type != JSON_TYPE.OBJECT) info.additionalJSON = parseJSON("{}");
273 
274 		auto minecraft = "minecraft" in info.additionalJSON;
275 		if(minecraft && minecraft.type == JSON_TYPE.OBJECT) {
276 			auto edu = "edu" in *minecraft;
277 			config.hub.edu = edu && edu.type == JSON_TYPE.TRUE;
278 		}
279 
280 		config.hub.displayName = info.displayName;
281 
282 		this.n_online = info.online;
283 		this.n_max = info.max;
284 
285 		auto social = "social" in info.additionalJSON;
286 		if(social && social.type == JSON_TYPE.OBJECT) {
287 			if("website" in *social) this.n_social.website = (*social)["website"].str;
288 			if("facebook" in *social) this.n_social.facebook = (*social)["facebook"].str;
289 			if("twitter" in *social) this.n_social.twitter = (*social)["twitter"].str;
290 			if("youtube" in *social) this.n_social.youtube = (*social)["youtube"].str;
291 			if("instagram" in *social) this.n_social.instagram = (*social)["instagram"].str;
292 			if("google-plus" in *social) this.n_social.googlePlus = (*social)["google-plus"].str;
293 		}
294 
295 		if(!this.lite) this.logger.terminal.setTitle(info.displayName ~ " | node | " ~ Software.simpleDisplay);
296 
297 		void handleGameInfo(ubyte type, HncomLogin.HubInfo.GameInfo info) {
298 			void set(ref Config.Hub.Game game) {
299 				game.enabled = true;
300 				game.protocols = info.protocols;
301 				game.motd = info.motd;
302 				game.onlineMode = info.onlineMode;
303 			}
304 			if(type == __JAVA__) {
305 				set(config.hub.java);
306 			} else if(type == __BEDROCK__) {
307 				set(config.hub.bedrock);
308 			} else {
309 				this.logger.logError(Translation("warning.invalidGame", [to!string(type), Software.name]));
310 			}
311 		}
312 
313 		foreach(game, info ; info.gamesInfo) {
314 			handleGameInfo(game, info);
315 		}
316 
317 		// check protocols and print warnings if necessary
318 		void check(string name, uint[] requested, uint[] supported) {
319 			foreach(req ; requested) {
320 				if(!supported.canFind(req)) {
321 					this.logger.logWarning(Translation("warning.invalidProtocol", [to!string(req), name]));
322 				}
323 			}
324 		}
325 
326 		check("Minecraft: Java Edition", config.hub.java.protocols, supportedJavaProtocols);
327 		check("Minecraft (Bedrock Engine)", config.hub.bedrock.protocols, supportedBedrockProtocols);
328 
329 		this._config = cast(shared)config;
330 
331 		this.finishConstruction();
332 
333 	}
334 
335 	private shared void finishConstruction() {
336 
337 		if(!this.lite) this.logger.log(Translation("startup.starting", [Format.green ~ Software.name ~ Format.white ~ " " ~ Software.fullVersion ~ Format.reset ~ " " ~ Software.fullCodename]));
338 
339 		static if(!__supported) {
340 			this.logger.logWarning(Translation("startup.unsupported", [Software.name]));
341 		}
342 
343 		this.globalListener = new EventListener!WorldEvent();
344 
345 		// default skins for players that connect with invalid skins
346 		Skin.STEVE = Skin("Standard_Steve", read_png_from_mem(cast(ubyte[])this.config.files.readAsset("skin/steve.png")).pixels);
347 		Skin.ALEX = Skin("Standard_Alex", read_png_from_mem(cast(ubyte[])this.config.files.readAsset("skin/alex.png")).pixels);
348 
349 		// load creative inventories
350 		foreach(protocol ; SupportedBedrockProtocols) {
351 			string[] failed;
352 			if(this.config.hub.bedrock.protocols.canFind(protocol)) {
353 				if(!mixin("BedrockPlayerImpl!" ~ protocol.to!string).loadCreativeInventory(this.config.files)) {
354 					failed ~= bedrockSupportedProtocols[protocol];
355 				}
356 			}
357 			if(failed.length) {
358 				this.logger.logWarning(Translation("warning.creativeFailed", [failed.join(", ")]));
359 			}
360 		}
361 
362 		// create resource pack files
363 		string[] textures = []; // ordered from least prioritised to most prioritised
364 		foreach_reverse(_plugin ; this.n_plugins) {
365 			auto plugin = cast()_plugin;
366 			if(plugin.textures !is null) textures ~= plugin.textures;
367 		}
368 		if(textures.length) {
369 			
370 			this.logger.log(Translation("startup.resourcePacks"));
371 
372 			auto rp_uuid = this.nextUUID;
373 			auto rp = createResourcePacks(this, rp_uuid, textures);
374 			std.concurrency.spawn(&serveResourcePacks, std.concurrency.thisTid, cast(string)rp.java2.idup, cast(string)rp.java3.idup);
375 			ushort port = std.concurrency.receiveOnly!ushort();
376 
377 			import myip : publicAddress4;
378 
379 			auto ip = publicAddress4;
380 			//TODO also try to use private addresses before using 127.0.0.1
381 
382 			JavaPlayer.updateResourcePacks(rp.java2, rp.java3, ip.length ? ip : "127.0.0.1", port);
383 			BedrockPlayer.updateResourcePacks(rp_uuid, rp.pocket1);
384 
385 		}
386 
387 		foreach(_plugin ; this.n_plugins) {
388 			auto plugin = cast()_plugin;
389 			plugin.load(this);
390 			auto args = [
391 				Format.green ~ plugin.name ~ Format.reset,
392 				Format.white ~ (plugin.authors.length ? plugin.authors.join(Format.reset ~ ", " ~ Format.white) : "?") ~ Format.reset,
393 				Format.white ~ plugin.vers
394 			];
395 			this.logger.log(Translation("startup.plugin.enabled" ~ (plugin.authors.length ? ".author" : (!plugin.vers.startsWith("~") ? ".version" : "")), args));
396 		}
397 		
398 		// register commands if enabled in the settings
399 		Commands.register(this);
400 
401 		// send node's informations to the hub and switch to a non-blocking connection
402 		HncomLogin.NodeInfo nodeInfo;
403 		uint[][ubyte] games;
404 		if(this.config.node.bedrock) nodeInfo.acceptedGames[__BEDROCK__] = cast(uint[])this.config.node.bedrock.protocols;
405 		if(this.config.node.java) nodeInfo.acceptedGames[__JAVA__] = cast(uint[])this.config.node.java.protocols;
406 		nodeInfo.max = this.config.node.maxPlayers; // 0 for unlimited, like in the config file
407 		foreach(_plugin ; this.n_plugins) {
408 			auto plugin = cast()_plugin;
409 			nodeInfo.plugins ~= HncomLogin.NodeInfo.Plugin(plugin.name, plugin.vers);
410 		}
411 		if(this.lite) {
412 			std.concurrency.send(cast()(cast(shared MessagePassingHandler)this.handler).hub, cast(shared)nodeInfo);
413 		} else {
414 			this.handler.send(nodeInfo.encode());
415 		}
416 		
417 		// load plugin's language files
418 		foreach(_plugin ; this.n_plugins) {
419 			auto plugin = cast()_plugin;
420 			if(plugin.languages !is null) {
421 				foreach(language, messages; this.config.lang.parseFolder(plugin.languages)) {
422 					this.updateLanguageFiles(language, messages);
423 				}
424 			}
425 		}
426 
427 		if(!this.lite) std.concurrency.spawn(&this.handler.receiveLoop, cast()this.tid);
428 		
429 		this.start_time = milliseconds;
430 
431 		// call @start functions
432 		foreach(plugin ; this.n_plugins) {
433 			foreach(del ; plugin.onstart) {
434 				del();
435 			}
436 		}
437 		
438 		if(this._default_world_id == 0) {
439 			//TODO load world in worlds/world
440 			this.addWorld("world");
441 		}
442 		
443 		version(Windows) {
444 			SetConsoleCtrlHandler(&sigHandler, true);
445 		} else version(linux) {
446 			sigset(SIGTERM, &extsig);
447 			sigset(SIGINT, &extsig);
448 		}
449 
450 		this.logger.log(Translation("startup.started"));
451 
452 		Terminal* terminal = (cast()this._logger).terminal;
453 		assert(terminal);
454 		if(this.lite) {
455 			this._logger = this.serverlogger = cast(shared)new LiteServerLogger(terminal, this.lang);
456 		} else {
457 			this._logger = this.serverlogger = cast(shared)new NodeServerLogger(terminal, this.lang);
458 		}
459 
460 		// start calculation of used resources
461 		std.concurrency.spawn(&startResourceUsageThread, getpid);
462 
463 		// start command reader
464 		std.concurrency.spawn(&startCommandReaderThread, cast()this.tid);
465 		
466 		this.start();
467 
468 	}
469 
470 	private shared void start() {
471 		
472 		//TODO request first latency calculation
473 
474 		while(running) {
475 
476 			// receive messages
477 			std.concurrency.receive(
478 				&handlePromptCommand,
479 				&handleCloseResult,
480 				(immutable(ubyte)[] payload){
481 					// from the hub
482 					if(payload.length) {
483 						(cast()this).handleHncom(payload.dup);
484 					} else {
485 						// close
486 						running = false;
487 					}
488 				},
489 				(Stop stop){
490 					running = false;
491 				},
492 			);
493 			
494 		}
495 		
496 		this.handler.close();
497 
498 		// call @stop plugins
499 		foreach(plugin ; this.n_plugins) {
500 			foreach(void delegate() del ; (cast()plugin).onstop) {
501 				del();
502 			}
503 		}
504 
505 		this.logger.log(Translation("startup.stopped"));
506 
507 		/*version(Windows) {
508 			// perform suicide
509 			executeShell("taskkill /PID " ~ to!string(getpid) ~ " /F");
510 		} else {*/
511 			import std.c.stdlib : exit;
512 			exit(0);
513 		//}
514 
515 	}
516 
517 	/**
518 	 * Stops the server setting the running variable to false and kicks every
519 	 * player from the server.
520 	 */
521 	public shared void shutdown() {
522 		std.concurrency.send(cast()server_tid, Stop());
523 	}
524 
525 	/**
526 	 * Gets the server's id, which is equal in the hub and all
527 	 * connected nodes.
528 	 * It is generated by SEL's snooping system or randomly if
529 	 * the service cannot be reached.
530 	 */
531 	public shared pure nothrow @property @safe @nogc immutable(long) id() {
532 		return this.n_id;
533 	}
534 
535 	public shared pure nothrow @property @nogc UUID nextUUID() {
536 		ubyte[16] data;
537 		data[0..8] = nativeToBigEndian(this.id);
538 		data[8..16] = nativeToBigEndian(this.uuid_count);
539 		atomicOp!"+="(this.uuid_count, 1);
540 		return UUID(data);
541 	}
542 
543 	/**
544 	 * Gets the arguments the server has been launched with, excluding
545 	 * the ones used by sel or the manager.
546 	 * Example:
547 	 * ---
548 	 * // from command-line
549 	 * ./node --name=test -a -b -p=test
550 	 * assert(server.args == ["-a", "-b"]);
551 	 * 
552 	 * // from sel manager
553 	 * sel start test --name=test -a --loop -b
554 	 * assert(server.args == ["-a", "-b"]);
555 	 * ---
556 	 * Custom arguments can be used by plugins to load optional settings.
557 	 * Example:
558 	 * ---
559 	 * @start load() {
560 	 *    if(!server.args.canFind("--disable-example")) {
561 	 *       this.loadImpl();
562 	 *    }
563 	 * }
564 	 * ---
565 	 */
566 	public shared pure nothrow @property @safe @nogc const args() {
567 		return this.n_args;
568 	}
569 
570 	public override shared pure nothrow @property @trusted @nogc const(Config) config() {
571 		return cast()this._config;
572 	}
573 
574 	public override shared @property Logger logger() {
575 		return cast()this._logger;
576 	}
577 
578 	public shared void logCommand(Message[] messages, int commandId) {
579 		(cast()this.serverlogger).logWith(messages, commandId);
580 	}
581 
582 	public shared void logWorld(Message[] messages, int worldId) {
583 		(cast()this.serverlogger).logWith(messages, HncomStatus.Log.NO_COMMAND, worldId);
584 	}
585 
586 	public override shared pure nothrow @property @trusted @nogc const(Plugin)[] plugins() {
587 		return cast(const(Plugin)[])this.n_plugins;
588 	}
589 
590 	/**
591 	 * Gets the server's name, as indicated in the hub's
592 	 * settings.txt file.
593 	 * The name should just the name of the server without
594 	 * any formatting code nor description.
595 	 * Example:
596 	 * ---
597 	 * "Potato Empire" // do
598 	 * "potato empire" // don't
599 	 * "Potato Empire: NEW MINIGAMES!" // don't
600 	 * "§aPotato §5Empire" // don't
601 	 * ---
602 	 */
603 	public shared pure nothrow @property @safe @nogc string name() {
604 		return this._config.hub.displayName;
605 	}
606 	
607 	/**
608 	 * Gets the number of online players in the current
609 	 * node (not in the whole server).
610 	 */
611 	public shared pure nothrow @property @safe @nogc size_t online() {
612 		return this._players.length;
613 	}
614 
615 	/**
616 	 * Gets the maximum number of players that can connect on the
617 	 * current node (not in the whole server).
618 	 * If the value is 0 there's no limit.
619 	 */
620 	public shared pure nothrow @property @safe @nogc size_t max() {
621 		return this._config.node.maxPlayers;
622 	}
623 
624 	/**
625 	 * Sets the maximum number of players that can connect to the
626 	 * current node.
627 	 * 0 can be used for unlimited players.
628 	 */
629 	public shared @property size_t max(uint max) {
630 		this._config.node.maxPlayers = max;
631 		this.handler.send(HncomStatus.UpdateMaxPlayers(max).encode());
632 		return max;
633 	}
634 	
635 	/**
636 	 * Gets the number of online players in the whole server
637 	 * (not just in the current node).
638 	 */
639 	public shared pure nothrow @property @safe @nogc size_t hubOnline() {
640 		return this.n_online;
641 	}
642 	
643 	/**
644 	 * Gets the number of maximum players that can connect to
645 	 * the hub.
646 	 * If the value is 0 it means that no limit has been set and
647 	 * players will never be kicked because the server is full.
648 	 */
649 	public shared pure nothrow @property @safe @nogc size_t hubMax() {
650 		return this.n_max;
651 	}
652 
653 	/**
654 	 * Gets the current's node name.
655 	 */
656 	public shared pure nothrow @property @safe @nogc string nodeName() {
657 		return this.config.node.name;
658 	}
659 
660 	/**
661 	 * Gets whether or not this is a main node (players are added
662 	 * when connected to hub) or not (player are added only when
663 	 * transferred by other nodes).
664 	 */
665 	public shared pure nothrow @property @safe @nogc bool isMainNode() {
666 		return this.config.node.main;
667 	}
668 
669 	/**
670 	 * Gets the server's social informations like website and social
671 	 * networks' names.
672 	 * Example:
673 	 * ---
674 	 * if(server.social.facebook.length) {
675 	 *    world.broadcast("Follow us on facebook! facebook.com/" ~ server.social.facebook);
676 	 * }
677 	 * ---
678 	 */
679 	public shared pure nothrow @property @safe @nogc const social() {
680 		return this.n_social;
681 	}
682 
683 	/**
684 	 * Gets the server's website.
685 	 * Example:
686 	 * ---
687 	 * assert(server.website == server.social.website);
688 	 * ---
689 	 */
690 	public shared pure nothrow @property @safe @nogc string website() {
691 		return this.social.website;
692 	}
693 
694 	/**
695 	 * Gets the address of the hub this node is connected to.
696 	 * Returns: the address of the hub connected to, or null if not connected yet
697 	 * Example:
698 	 * ---
699 	 * if(server.hubAddress.toAddrString() == "127.0.0.1") {
700 	 *    writeln("the hub is ipv4 localhost!");
701 	 * } else if(server.hubAddress.toAddrString() == "::1") {
702 	 *    writeln("The hub is ipv6 localhost!");
703 	 * }
704 	 * ---
705 	 */
706 	public shared pure nothrow @property @trusted @nogc Address hubAddress() {
707 		return cast()this.n_hub_address;
708 	}
709 
710 	/**
711 	 * Gets the latency between the node and the hub.
712 	 */
713 	public shared pure nothrow @property @safe @nogc uint hubLatency() {
714 		return this.n_hub_latency;
715 	}
716 
717 	/**
718 	 * Gets the server's uptime as a Duration instance with milliseconds precision.
719 	 * The count starts when the node is duccessfully connected with the hub.
720 	 * Example:
721 	 * ---
722 	 * writeln("The server is online from ", server.uptime.minutes, " minutes");
723 	 * 
724 	 * ulong m, s;
725 	 * server.uptime.split!("minutes", "seconds")(m, s);
726 	 * writeln("The server is online from ", m, " minutes and ", s, " seconds");
727 	 * ---
728 	 */
729 	public shared @property @safe Duration uptime() {
730 		return dur!"msecs"(milliseconds - this.start_time);
731 	}
732 
733 	/**
734 	 * Gets a list with the nodes connected to the hub, this excluded.
735 	 * Example:
736 	 * ---
737 	 * foreach(node ; server.nodes) {
738 	 *    assert(node.name != server.nodeName);
739 	 * }
740 	 * ---
741 	 */
742 	public shared pure nothrow @property @trusted const(Node)[] nodes() {
743 		return cast(const(Node)[])this.nodes_hubid.values;
744 	}
745 
746 	/**
747 	 * Gets a node by its name. It can be used to transfer players
748 	 * to it.
749 	 * Example:
750 	 * ---
751 	 * auto lobby = server.nodeWithName("lobby");
752 	 * if(lobby !is null) lobby.transfer(player);
753 	 * ---
754 	 */
755 	public shared inout pure nothrow @trusted const(Node) nodeWithName(string name) {
756 		auto ret = name in this.nodes_names;
757 		return ret ? cast(const)*ret : null;
758 	}
759 
760 	/**
761 	 * Gets a node by its hub id, which is given by the hub and
762 	 * unique for every session.
763 	 */
764 	public shared inout pure nothrow @trusted const(Node) nodeWithHubId(uint hubId) {
765 		auto ret = hubId in this.nodes_hubid;
766 		return ret ? cast(const)*ret : null;
767 	}
768 
769 	/**
770 	 * Sends a message to a node.
771 	 */
772 	public shared void sendMessage(Node[] nodes, ubyte[] payload) {
773 		uint[] addressees;
774 		foreach(node ; nodes) {
775 			if(node !is null) addressees ~= node.hubId;
776 		}
777 		this.handler.send(HncomStatus.SendMessage(addressees, payload).encode());
778 	}
779 
780 	/// ditto
781 	public shared void sendMessage(Node node, ubyte[] payload) {
782 		this.sendMessage([node], payload);
783 	}
784 
785 	/**
786 	 * Broadcasts a message to every node connected to the hub.
787 	 */
788 	public shared void broadcast(ubyte[] payload) {
789 		this.sendMessage([], payload);
790 	}
791 
792 	/**
793 	 * Updates langauge files (in config) and send the UpdateLanguageFiles packet to
794 	 * the hub if needed.
795 	 */
796 	protected shared void updateLanguageFiles(string language, string[string] messages) {
797 		if(!this.lite) this.config.lang.add(language, messages);
798 		this.handler.send(HncomStatus.UpdateLanguageFiles(language, messages).encode());
799 	}
800 
801 	/**
802 	 * Gets the server's default world.
803 	 */
804 	public shared pure nothrow @property shared(WorldInfo) defaultWorld() {
805 		return this._worlds[this._default_world_id];
806 	}
807 
808 	/**
809 	 * Sets the server's default world if the given world has been
810 	 * created and registered using the addWorld template method.
811 	 * Returns: The server's default world (the given world if it has been set as default)
812 	 * Example:
813 	 * ---
814 	 * auto test = server.addWorld!Test("test-world");
815 	 * server.world = test;
816 	 * assert(server.world == test);
817 	 * ---
818 	 */
819 	public shared pure nothrow @property shared(WorldInfo) defaultWorld(uint id) {
820 		if(id in this._worlds) {
821 			this._default_world_id = id;
822 		}
823 		return this.defaultWorld;
824 	}
825 
826 	/// ditto
827 	public shared pure nothrow @property shared(WorldInfo) defaultWorld(shared WorldInfo world) {
828 		return this.defaultWorld = world.id;
829 	}
830 
831 	/**
832 	 * Gets a list with every world registered in the server.
833 	 * The list is a copy of the one kept by the server and its
834 	 * modification has no effect on the server.
835 	 */
836 	public shared pure nothrow @property shared(WorldInfo)[] worlds() {
837 		return this._worlds.values;
838 	}
839 
840 	/**
841 	 * Gets a world by its name.
842 	 * Returns: the WorldInfo of the world with the given name or null if a world with the given name doesn't exists.
843 	 */
844 	public shared @property shared(WorldInfo) getWorldByName(string name) {
845 		auto world = name in this._worlds_names;
846 		return world ? *world : null;
847 	}
848 
849 	/**
850 	 * Creates and registers a world with its group, initialising its terrain,
851 	 * registering events, commands and tasks.
852 	 * Returns: the WorldInfo of the created world or null if the a world with the same name already exists.
853 	 * Example:
854 	 * ---
855 	 * server.addWorld("world42"); // normal world
856 	 * server.addWorld!CustomWorld("custom", 42); // custom world where 42 is passed to the constructor
857 	 * ---
858 	 */
859 	public shared synchronized shared(WorldInfo) addWorld(T:World=World, E...)(string name, E args) /*if(__traits(compiles, new T(args)))*/ {
860 		if(name !in this._worlds_names) {
861 			shared WorldInfo world = cast(shared)new WorldInfo(atomicOp!"+="(this._world_count, 1), name);
862 			this._worlds[world.id] = world;
863 			this._worlds_names[world.name] = world;
864 			bool default_ = this._default_world_id == 0;
865 			if(default_) this._default_world_id = world.id;
866 			world.tid = cast(shared)std.concurrency.spawn(&spawnWorld!(T, E), cast(shared)this, world, default_, args);
867 			return world;
868 		} else {
869 			return null;
870 		}
871 	}
872 
873 	/**
874 	 * Removes a world and unloads it.
875 	 * When trying to remove the default world a message error will be
876 	 * displayed and the world will not be unloaded.
877 	 */
878 	public shared synchronized bool removeWorld(uint id) {
879 		auto world = id in this._worlds;
880 		if(world) {
881 			if((*world).id == this._default_world_id) {
882 				this.logger.logWarning(Translation("warning.removingDefaultWorld", (*world).name));
883 			} else {
884 				std.concurrency.send(cast()world.tid, Close()); // wait for CloseResult before removing the world
885 				return true;
886 			}
887 		}
888 		return false;
889 	}
890 
891 	/// ditto
892 	public shared bool removeWorld(WorldInfo world) {
893 		return this.removeWorld(world.id);
894 	}
895 
896 	protected shared void handleCloseResult(CloseResult result) {
897 		auto world = result.worldId in this._worlds;
898 		if(world) {
899 			if(result.status == CloseResult.PLAYERS_ONLINE) {
900 				this.logger.logWarning(Translation("warning.removingWithPlayers", (*world).name));
901 			} else {
902 				this._worlds.remove((*world).id);
903 				this._worlds_names.remove((*world).name);
904 			}
905 		}
906 	}
907 
908 	/**
909 	 * Gets a list with all the players in the server.
910 	 */
911 	public shared pure nothrow @property shared(PlayerInfo)[] players() {
912 		return this._players.values;
913 	}
914 
915 	/**
916 	 * Broadcasts a message in every registered world and their children
917 	 * calling the world's broadcast method.
918 	 */
919 	public shared void broadcast(string message) {
920 		foreach(world ; this._worlds) {
921 			std.concurrency.send(cast()world.tid, Broadcast(message));
922 		}
923 	}
924 
925 	public shared void updateWorldDifficulty(shared WorldInfo world, Difficulty difficulty) {
926 		std.concurrency.send(cast()world.tid, UpdateDifficulty(difficulty));
927 	}
928 
929 	public shared void updatePlayerGamemode(shared PlayerInfo player, Gamemode gamemode) {
930 		std.concurrency.send(cast()player.world.tid, UpdatePlayerGamemode(player.hubId, gamemode));
931 	}
932 
933 	public shared void updatePlayerPermissionLevel(shared PlayerInfo player, PermissionLevel permissionLevel) {
934 		std.concurrency.send(cast()player.world.tid, UpdatePlayerPermissionLevel(player.hubId, permissionLevel));
935 	}
936 
937 	/**
938 	 * Registers a command.
939 	 */
940 	public void registerCommand(alias func)(void delegate(Parameters!func) del, string command, Description description, string[] aliases, ubyte permissionLevel, string[] permissions, bool hidden, bool implemented=true) {
941 		if(command !in this._commands) this._commands[command] = cast(shared)new Command(command, description, aliases, permissionLevel, permissions, hidden);
942 		auto ptr = command in this._commands;
943 		(cast()*ptr).add!func(del, implemented);
944 		foreach(alias_ ; aliases) this._commands[alias_] = *ptr;
945 	}
946 
947 	public shared @property auto commands() {
948 		return this._commands;
949 	}
950 
951 	// hub-node communication and related methods
952 
953 	/*
954 	 * Kicks a player from the server using Player.kick.
955 	 */
956 	public shared void kick(uint hubId, string reason) {
957 		if(this.removePlayer(hubId, PlayerLeftEvent.Reason.kicked)) {
958 			this.handler.send(HncomPlayer.Kick(hubId, reason, false).encode());
959 		}
960 	}
961 
962 	/// ditto
963 	public shared void kick(uint hubId, string reason, inout(string)[] args) {
964 		if(this.removePlayer(hubId, PlayerLeftEvent.Reason.kicked)) {
965 			this.handler.send(HncomPlayer.Kick(hubId, reason, true, cast(string[])args).encode());
966 		}
967 	}
968 
969 	/*
970 	 * Transfers a player to another node using Player.transfer
971 	 */
972 	public shared void transfer(uint hubId, inout Node node) {
973 		if(this.removePlayer(hubId, PlayerLeftEvent.Reason.transferred)) {
974 			this.handler.send(HncomPlayer.Transfer(hubId, node.hubId).encode());
975 		}
976 	}
977 
978 	// removes with a reason a player spawned in the server
979 	private shared synchronized bool removePlayer(uint hubId, ubyte reason) {
980 		auto player = hubId in this._players;
981 		if(player) {
982 			if((*player).world !is null) {
983 				std.concurrency.send(cast()(*player).world.tid, RemovePlayer(hubId));
984 			}
985 			this._players.remove(hubId);
986 			(cast()this).callEventIfExists!PlayerLeftEvent(this, cast(const)*player, reason);
987 			return true;
988 		} else {
989 			return false;
990 		}
991 	}
992 	
993 	public shared void updatePlayerDisplayName(uint hubId) {
994 		auto player = hubId in this._players;
995 		if(player) this.handler.send(HncomPlayer.UpdateDisplayName(hubId, (*player).displayName).encode());
996 	}
997 
998 	// hncom handlers
999 
1000 	protected override void handleUtilUncompressed(HncomUtil.Uncompressed packet) {
1001 		assert(packet.id == 0); //TODO
1002 		foreach(p ; packet.packets) {
1003 			if(p.length) this.handleHncom(p.dup);
1004 		}
1005 	}
1006 
1007 	protected override void handleUtilCompressed(HncomUtil.Compressed packet) {
1008 		this.handleUtilUncompressed(packet.uncompress());
1009 	}
1010 
1011 	protected override void handleStatusLatency(HncomStatus.Latency packet) {
1012 		//TODO send packet back
1013 	}
1014 	
1015 	protected override void handleStatusRemoteCommand(HncomStatus.RemoteCommand packet) {
1016 		with(packet) (cast(shared)this).handleCommand(cast(ubyte)(origin + 1), sender, command, commandId);
1017 	}
1018 
1019 	protected override void handleStatusAddNode(HncomStatus.AddNode packet) {
1020 		auto node = new Node(cast(shared)this, packet.hubId, packet.name, packet.main, packet.acceptedGames);
1021 		this.nodes_hubid[node.hubId] = cast(shared)node;
1022 		this.nodes_names[node.name] = cast(shared)node;
1023 		this.callEventIfExists!NodeAddedEvent(node);
1024 	}
1025 
1026 	protected override void handleStatusRemoveNode(HncomStatus.RemoveNode packet) {
1027 		auto node = packet.hubId in this.nodes_hubid;
1028 		if(node) {
1029 			this.nodes_hubid.remove((*node).hubId);
1030 			this.nodes_names.remove((*node).name);
1031 			this.callEventIfExists!NodeRemovedEvent(cast()*node);
1032 		}
1033 	}
1034 
1035 	protected override void handleStatusReceiveMessage(HncomStatus.ReceiveMessage packet) {
1036 		auto node = (cast(shared)this).nodeWithHubId(packet.sender);
1037 		// only accept message from nodes that didn't disconnect
1038 		if(node !is null) {
1039 			this.callEventIfExists!NodeMessageEvent(cast()node, cast(ubyte[])packet.payload);
1040 		}
1041 	}
1042 
1043 	protected override void handleStatusUpdatePlayers(HncomStatus.UpdatePlayers packet) {
1044 		this.n_online = packet.online;
1045 		this.n_max = packet.max;
1046 	}
1047 
1048 	protected override void handleStatusUpdateDisplayName(HncomStatus.UpdateDisplayName packet) {
1049 		this._config.hub.displayName = packet.displayName;
1050 	}
1051 
1052 	protected override void handleStatusUpdateMOTD(HncomStatus.UpdateMOTD packet) {
1053 		// assuming that is already parsed
1054 		if(packet.type == __BEDROCK__) this._config.hub.bedrock.motd = packet.motd;
1055 		else if(packet.type == __JAVA__) this._config.hub.java.motd = packet.motd;
1056 	}
1057 
1058 	protected override void handleStatusUpdateSupportedProtocols(HncomStatus.UpdateSupportedProtocols packet) {
1059 		//TODO
1060 	}
1061 
1062 	protected override void handleStatusWebAdminCredentials(HncomStatus.WebAdminCredentials packet) {
1063 		//TODO start http server for panel
1064 	}
1065 
1066 	protected override void handlePlayerAdd(HncomPlayer.Add packet) {
1067 
1068 		Skin skin = Skin(packet.skin.expand);
1069 
1070 		if(!skin.valid) {
1071 			// http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/java/util/UUID.java#l394
1072 			ubyte a = packet.uuid.data[7] ^ packet.uuid.data[15];
1073 			ubyte b = (packet.uuid.data[3] ^ packet.uuid.data[11]) ^ a;
1074 			skin = ((b & 1) == 0) ? Skin.STEVE : Skin.ALEX;
1075 		}
1076 
1077 		shared PlayerInfo player = cast(shared)new PlayerInfo(packet);
1078 		player.skin = skin;
1079 
1080 		// add to the lists
1081 		this._players[player.hubId] = cast(shared)player;
1082 
1083 		auto event = (cast()this).callEventIfExists!PlayerJoinEvent(cast(shared)this, cast(const)player, packet.reason);
1084 
1085 		//TODO allow kicking from event
1086 
1087 		shared(WorldInfo) world;
1088 		if(event is null || event.world is null || event.world.id !in this._worlds) {
1089 			world = this._worlds[this._default_world_id];
1090 		} else {
1091 			world = this._worlds[event.world.id];
1092 		}
1093 
1094 		// do not spawn if it has been disconnected during the event
1095 		if(player.hubId in this._players) {
1096 
1097 			std.concurrency.send(cast()world.tid, AddPlayer(player, packet.reason != HncomPlayer.Add.FIRST_JOIN));
1098 
1099 		}
1100 
1101 	}
1102 
1103 	protected override void handlePlayerRemove(HncomPlayer.Remove packet) {
1104 		(cast(shared)this).removePlayer(packet.hubId, packet.reason);
1105 	}
1106 
1107 	protected override void handlePlayerUpdateDisplayName(HncomPlayer.UpdateDisplayName packet) {
1108 		//TODO
1109 	}
1110 
1111 	protected override void handlePlayerUpdatePermissionLevel(HncomPlayer.UpdatePermissionLevel packet) {
1112 		auto player = packet.hubId in this._players;
1113 		if(player) {
1114 			(cast(shared)this).updatePlayerPermissionLevel(*player, cast(PermissionLevel)packet.permissionLevel);
1115 		}
1116 	}
1117 
1118 	protected override void handlePlayerUpdateViewDistance(HncomPlayer.UpdateViewDistance packet) {
1119 		//TODO
1120 	}
1121 
1122 	protected override void handlePlayerUpdateLanguage(HncomPlayer.UpdateLanguage packet) {
1123 		//TODO
1124 	}
1125 
1126 	protected override void handlePlayerUpdateLatency(HncomPlayer.UpdateLatency packet) {
1127 		//TODO
1128 	}
1129 
1130 	protected override void handlePlayerUpdatePacketLoss(HncomPlayer.UpdatePacketLoss packet) {
1131 		//TODO
1132 	}
1133 
1134 	protected override void handlePlayerGamePacket(HncomPlayer.GamePacket packet) {
1135 		auto player = packet.hubId in this._players;
1136 		if(player && packet.payload.length) {
1137 			std.concurrency.send(cast()(*player).world.tid, GamePacket(packet.hubId, packet.payload.idup));
1138 		}
1139 	}
1140 
1141 	private shared void handlePromptCommand(string command) {
1142 		this.handleCommand(0, null, command);
1143 	}
1144 
1145 	// handles a command from various sources.
1146 	private shared void handleCommand(ubyte origin, Address address, string command, int id=-1) {
1147 		auto sender = new ServerCommandSender(this, this._commands, origin, address, id);
1148 		executeCommand(sender, command).trigger(sender);
1149 	}
1150 
1151 }
1152 
1153 final class ServerCommandSender : CommandSender {
1154 
1155 	enum Origin : ubyte {
1156 
1157 		prompt = 0,
1158 		hub = HncomStatus.RemoteCommand.HUB + 1,
1159 		webAdmin = HncomStatus.RemoteCommand.WEB_ADMIN + 1,
1160 		rcon = HncomStatus.RemoteCommand.RCON + 1,
1161 
1162 	}
1163 
1164 	private shared NodeServer _server;
1165 	private shared Command[string] _commands;
1166 	public immutable ubyte origin;
1167 	public const Address address;
1168 	private int id;
1169 
1170 	public this(shared NodeServer server, shared Command[string] commands, ubyte origin, Address address, int id) {
1171 		this._server = server;
1172 		this._commands = commands;
1173 		this.origin = origin;
1174 		this.address = address;
1175 		this.id = id;
1176 	}
1177 
1178 	public override pure nothrow @property @safe @nogc shared(NodeServer) server() {
1179 		return this._server;
1180 	}
1181 
1182 	public override @property Command[string] availableCommands() {
1183 		return cast(Command[string])this._commands;
1184 	}
1185 
1186 	protected override void sendMessageImpl(Message[] messages) {
1187 		this._server.logCommand(messages, this.id);
1188 	}
1189 
1190 	alias server this;
1191 
1192 }
1193 
1194 private abstract class ServerLogger : Logger {
1195 
1196 	public this(Terminal* terminal, inout LanguageManager lang) {
1197 		super(terminal, lang);
1198 	}
1199 
1200 	public override void logMessage(Message[] messages) {
1201 		this.logWith(messages);
1202 	}
1203 
1204 	/**
1205 	 * Creates a log with commandId and worldId.
1206 	 */
1207 	public void logWith(Message[] messages, int commandId=HncomStatus.Log.NO_COMMAND, int worldId=HncomStatus.Log.NO_WORLD) {
1208 		this.logWithImpl(messages, commandId, worldId);
1209 	}
1210 
1211 	protected abstract void logWithImpl(Message[], int, int);
1212 
1213 }
1214 
1215 private class NodeServerLogger : ServerLogger {
1216 
1217 	public this(Terminal* terminal, inout LanguageManager lang) {
1218 		super(terminal, lang);
1219 	}
1220 
1221 	// prints to the console and send to the hub
1222 	protected override void logWithImpl(Message[] messages, int commandId, int worldId) {
1223 		this.logImpl(messages);
1224 		Handler.sharedInstance.send(HncomStatus.Log(encodeHncomMessage(messages), milliseconds, commandId, worldId).encode());
1225 	}
1226 
1227 }
1228 
1229 private class LiteServerLogger : ServerLogger {
1230 
1231 	public this(Terminal* terminal, inout LanguageManager lang) {
1232 		super(terminal, lang);
1233 	}
1234 
1235 	// only send to the hub
1236 	protected override void logWithImpl(Message[] messages, int commandId, int worldId) {
1237 		Handler.sharedInstance.send(HncomStatus.Log(encodeHncomMessage(messages), milliseconds, commandId, worldId).encode());
1238 	}
1239 
1240 }
1241 
1242 private HncomStatus.Log.Message[] encodeHncomMessage(Message[] messages) {
1243 	HncomStatus.Log.Message[] ret;
1244 	string next;
1245 	void addText() {
1246 		ret ~= HncomStatus.Log.Message(false, next, []);
1247 		next.length = 0;
1248 	}
1249 	foreach(message ; messages) {
1250 		final switch(message.type) {
1251 			case Message.FORMAT:
1252 				next ~= message.format;
1253 				break;
1254 			case Message.TEXT:
1255 				next ~= message.text;
1256 				break;
1257 			case Message.TRANSLATION:
1258 				if(next.length) addText();
1259 				ret ~= HncomStatus.Log.Message(true, message.translation.translatable.default_, message.translation.parameters);
1260 				break;
1261 		}
1262 	}
1263 	if(next.length) addText();
1264 	return ret;
1265 }
1266 
1267 private void startResourceUsageThread(int pid) {
1268 
1269 	debug Thread.getThis().name = "ResourcesUsage";
1270 	
1271 	ProcessMemInfo ram = processMemInfo(pid);
1272 	ProcessCPUWatcher cpu = new ProcessCPUWatcher(pid);
1273 	
1274 	while(true) {
1275 		ram.update();
1276 		//TODO send packet directly to the socket
1277 		Handler.sharedInstance.send(HncomStatus.UpdateUsage(cast(uint)(ram.usedRAM / 1024u), cpu.current()).encode());
1278 		Thread.sleep(dur!"seconds"(5));
1279 	}
1280 	
1281 }
1282 
1283 //TODO use Terminal
1284 private void startCommandReaderThread(std.concurrency.Tid tid) {
1285 
1286 	debug Thread.getThis().name = "CommandReader";
1287 
1288 	import std.stdio : readln;
1289 	while(true) {
1290 		std.concurrency.send(tid, readln());
1291 	}
1292 
1293 }