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