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/world/group.d, selery/world/group.d)
28  */
29 module selery.world.group;
30 
31 import core.atomic : atomicOp;
32 import core.thread : Thread;
33 
34 import std.concurrency : Tid, send, receiveTimeout;
35 import std.conv : to;
36 import std.datetime : dur;
37 import std.datetime.stopwatch : StopWatch;
38 import std.random : Random, unpredictableSeed, uniform, dice;
39 import std..string : toUpper;
40 import std.traits : isAbstractClass, Parameters;
41 import std.typetuple : TypeTuple;
42 
43 import selery.about : SupportedBedrockProtocols, SupportedJavaProtocols;
44 import selery.config : Difficulty, Gamemode, Config;
45 import selery.hncom.about : __BEDROCK__, __JAVA__;
46 import selery.hncom.status : HncomAddWorld = AddWorld, HncomRemoveWorld = RemoveWorld;
47 import selery.log : Message;
48 import selery.math.vector : EntityPosition;
49 import selery.node.handler : Handler;
50 import selery.node.server : NodeServer;
51 import selery.player.bedrock : BedrockPlayerImpl;
52 import selery.player.java : JavaPlayerImpl;
53 import selery.player.player : PlayerInfo, Player, PermissionLevel;
54 import selery.util.util : call;
55 import selery.world.plugin : loadWorld;
56 import selery.world.world : WorldInfo, World;
57 
58 private shared uint _id;
59 
60 /**
61  * Generic informations about a group of worlds.
62  */
63 final class GroupInfo {
64 	
65 	/**
66 	 * Group's id. It is unique on the node.
67 	 */
68 	public immutable uint id;
69 	
70 	/**
71 	 * World's name, which is given by the user who creates the world.
72 	 * It is unique in the node and every world in the same group has
73 	 * the same name.
74 	 */
75 	public immutable string name;
76 	
77 	/**
78 	 * Thread where the group exists.
79 	 */
80 	public Tid tid;
81 
82 	/**
83 	 * Worlds in the group.
84 	 */
85 	shared(WorldInfo)[uint] worlds;
86 
87 	/**
88 	 * Group's default world.
89 	 */
90 	shared WorldInfo defaultWorld;
91 	
92 	public shared this(string name) {
93 		this.id = atomicOp!"+="(_id, 1);
94 		this.name = name;
95 	}
96 	
97 }
98 
99 void spawnWorldGroup(shared NodeServer server, shared GroupInfo info, bool main) {
100 
101 	debug Thread.getThis().name = "world_group#" ~ to!string(info.id);
102 
103 	try {
104 
105 		WorldGroup group = new WorldGroup(server, info, main);
106 		group.start();
107 
108 	} catch(Throwable t) {
109 
110 		import selery.crash;
111 		logCrash("world", server.lang, t);
112 
113 	}
114 	
115 	//TODO catch exceptions per-world
116 	
117 }
118 
119 final class WorldGroup {
120 
121 	private shared NodeServer _server;
122 	private shared GroupInfo info;
123 	private bool main;
124 	private Random random;
125 
126 	private bool _stopped = false;
127 
128 	private World[uint] _worlds;
129 	private Player[uint] _players;
130 	
131 	Gamemode gamemode;
132 	Difficulty difficulty;
133 	
134 	// rules that the client does not need to know about
135 	bool pvp;
136 	bool naturalRegeneration;
137 	bool depleteHunger;
138 	uint randomTickSpeed;
139 	uint viewDistance;
140 	
141 	Time time;
142 	Weather weather;
143 	
144 	private this(shared NodeServer server, shared GroupInfo info, bool main) {
145 		this._server = server;
146 		this.info = info;
147 		this.main = main;
148 		this.random = Random(unpredictableSeed);
149 		with(server.config.node) {
150 			this.gamemode = gamemode;
151 			this.difficulty = difficulty;
152 			this.depleteHunger = depleteHunger;
153 			this.naturalRegeneration = naturalRegeneration;
154 			this.pvp = pvp;
155 			this.randomTickSpeed = randomTickSpeed;
156 			this.viewDistance = viewDistance;
157 			//TODO more rules
158 			this.time = new Time(doDaylightCycle);
159 			this.weather = new Weather(doWeatherCycle);
160 			this.weather.clear();
161 		}
162 	}
163 
164 	public pure nothrow @property @safe @nogc shared(NodeServer) server() {
165 		return this._server;
166 	}
167 
168 	public pure nothrow @property World[] worlds() {
169 		return this._worlds.values;
170 	}
171 
172 	public pure nothrow @property Player[] players() {
173 		return this._players.values;
174 	}
175 	
176 	//TODO constructor from world's save file
177 	
178 	// updates
179 	
180 	void addPlayer(Player player) {
181 		this._players.call!"sendAddList"([player]);
182 		this._players[player.hubId] = player;
183 		player.sendAddList(this.players);
184 	}
185 
186 	void removePlayer(Player player, bool closed) {
187 		if(this._players.remove(player.hubId)) {
188 			this._players.call!"sendRemoveList"([player]);
189 			if(!closed) player.sendRemoveList(this.players);
190 		}
191 	}
192 	
193 	void updateDifficulty(Difficulty difficulty) {
194 		this.difficulty = difficulty;
195 		this._players.call!"sendDifficulty"(difficulty);
196 	}
197 	
198 	void updateGamemode(Gamemode gamemode) {
199 		this.gamemode = gamemode;
200 		this.players.call!"sendWorldGamemode"(gamemode);
201 	}
202 	
203 	private class Time {
204 		
205 		bool _cycle;
206 		uint day;
207 		uint _time;
208 		
209 		this(bool cycle) {
210 			this._cycle = cycle;
211 		}
212 		
213 		@property bool cycle() {
214 			return _cycle;
215 		}
216 		
217 		@property bool cycle(bool cycle) {
218 			players.call!"sendDoDaylightCycle"(cycle);
219 			return _cycle = cycle;
220 		}
221 		
222 		@property uint time() {
223 			return _time;
224 		}
225 		
226 		@property uint time(uint time) {
227 			time %= 24000;
228 			players.call!"sendTime"(time);
229 			return _time = time;
230 		}
231 		
232 		alias time this;
233 		
234 	}
235 	
236 	private class Weather {
237 		
238 		public bool cycle;
239 		private bool _raining, _thunderous;
240 		private uint _time;
241 		private uint _intensity;
242 		
243 		this(bool cycle) {
244 			this.cycle = cycle;
245 		}
246 		
247 		@property bool raining() {
248 			return _raining;
249 		}
250 		
251 		@property bool thunderous() {
252 			return _thunderous;
253 		}
254 		
255 		@property uint intensity() {
256 			return _intensity;
257 		}
258 		
259 		void clear(uint duration) {
260 			_raining = _thunderous = false;
261 			_time = duration;
262 			_intensity = 0;
263 			update();
264 		}
265 		
266 		void clear() {
267 			clear(uniform!"[]"(12000u, 180000u, random));
268 		}
269 		
270 		void start(uint time, uint intensity, bool thunderous) {
271 			assert(intensity > 0);
272 			_raining = true;
273 			_thunderous = thunderous;
274 			_time = time;
275 			_intensity = intensity;
276 			update();
277 		}
278 		
279 		void start(uint time, bool thunderous) {
280 			start(time, uniform!"[]"(1, 4, random), thunderous);
281 		}
282 		
283 		void start() {
284 			start(uniform!"[]"(12000u, 24000u, random), !dice(random, .5, .5));
285 		}
286 		
287 		private void update() {
288 			players.call!"sendWeather"(_raining, _thunderous, _time, _intensity);
289 		}
290 		
291 	}
292 
293 	/**
294 	 * Starts the group.
295 	 */
296 	public void start() {
297 
298 		StopWatch timer;
299 		ulong duration;
300 		
301 		while(!this._stopped) {
302 			
303 			timer.start();
304 			
305 			// handle server's message (new worlds, new players, new packets, ...)
306 			this.handleServerPackets();
307 			
308 			// tick the group
309 			this.tick();
310 			
311 			// tick the worlds
312 			foreach(world ; this._worlds) world.tick();
313 			
314 			// flush player's packets
315 			foreach(player ; this._players) player.flush();
316 			
317 			// sleep until next tick
318 			timer.stop();
319 			timer.peek.split!"usecs"(duration);
320 			if(duration < 50_000) {
321 				Thread.sleep(dur!"usecs"(50_000 - duration));
322 			} else {
323 				//TODO server is less than 20 tps!
324 			}
325 			timer.reset();
326 			
327 		}
328 		
329 		//TODO make sure no players are online
330 
331 		foreach(world ; this._worlds) {
332 			world.stop();
333 		}
334 		
335 		//TODO send RemoveWorld to the hub (children will be removed automatically)
336 		
337 		send(cast()this.server.tid, CloseResult(this.info.id, CloseResult.REMOVED));
338 
339 	}
340 	
341 	private void handleServerPackets() {
342 		while(receiveTimeout(dur!"msecs"(0),
343 			&this.handleAddWorld,
344 			&this.handleRemoveWorld,
345 			&this.handleAddPlayer,
346 			&this.handleRemovePlayer,
347 			&this.handleGamePacket,
348 			&this.handleBroadcast,
349 			&this.handleUpdateDifficulty,
350 			&this.handleUpdatePlayerGamemode,
351 			&this.handleUpdatePlayerPermissionLevel,
352 			&this.handleClose,
353 		)) {}
354 	}
355 
356 	private void handleAddWorld(AddWorld packet) {
357 		(cast()packet.create).create(this);
358 	}
359 
360 	private void handleRemoveWorld(RemoveWorld packet) {
361 		this.removeWorld(packet.worldId);
362 	}
363 	
364 	private void handleAddPlayer(AddPlayer packet) {
365 		//TODO allow spawning in a child
366 		this.spawnPlayer(packet.player, packet.transferred);
367 	}
368 	
369 	private void handleRemovePlayer(RemovePlayer packet) {
370 		auto player = packet.playerId in this._players;
371 		if(player) {
372 			this.removePlayer(*player, false); //TODO whether the player has left the server
373 			(*player).world.despawnPlayer(*player);
374 			(*player).close();
375 		}
376 	}
377 	
378 	private void handleGamePacket(GamePacket packet) {
379 		auto player = packet.playerId in this._players;
380 		if(player) {
381 			(*player).handle(packet.payload[0], packet.payload[1..$].dup);
382 		}
383 	}
384 	
385 	private void handleBroadcast(Broadcast packet) {
386 		this.broadcast(packet.message);
387 	}
388 	
389 	private void handleUpdateDifficulty(UpdateDifficulty packet) {
390 		this.difficulty = packet.difficulty;
391 	}
392 	
393 	private void handleUpdatePlayerGamemode(UpdatePlayerGamemode packet) {
394 		auto player = packet.playerId in this._players;
395 		if(player) {
396 			(*player).gamemode = packet.gamemode;
397 		}
398 	}
399 	
400 	private void handleUpdatePlayerPermissionLevel(UpdatePlayerPermissionLevel packet) {
401 		auto player = packet.playerId in this._players;
402 		if(player) {
403 			(*player).permissionLevel = packet.permissionLevel;
404 		}
405 	}
406 	
407 	private void handleClose(Close packet) {
408 		if(this.players.length) {
409 			// cannot close if there are players online in the world or in the children
410 			// the world is not stopped
411 			send(cast()this.server.tid, CloseResult(this.info.id, CloseResult.PLAYERS_ONLINE));
412 		} else {
413 			// the world will be stopped at the end of the next tick
414 			this._stopped = true;
415 		}
416 	}
417 	
418 	private void tick() {
419 		
420 		if(this.time.cycle) {
421 			if(++this.time._time == 24000) {
422 				this.time._time = 0;
423 				this.time.day++;
424 			}
425 		}
426 		
427 		if(this.weather.cycle) {
428 			if(--this.weather._time == 0) {
429 				if(this.weather._raining) this.weather.clear();
430 				else this.weather.start();
431 			}
432 		}
433 		
434 	}
435 
436 	/**
437 	 * Broadcasts a message to every world in the group.
438 	 */
439 	public final void broadcast(E...)(E args) {
440 		static if(E.length == 1 && is(E[0] == Message[])) this.broadcastImpl(args[0]);
441 		else this.broadcastImpl(Message.convert(args));
442 	}
443 	
444 	protected void broadcastImpl(Message[] message) {
445 		foreach(world ; this._worlds) world.broadcast(message);
446 	}
447 
448 	/**
449 	 * Adds a world.
450 	 */
451 	public void addWorld(T:World=World, E...)(shared WorldInfo info, E args) {
452 		T world = new T(args);
453 		this.addWorldImpl(info, world);
454 		// register events and similar stuff
455 		world._update_state = delegate(int oldState, uint newState){ loadWorld(world, oldState, newState); };
456 		world.updateState(0);
457 		world.start(this);
458 	}
459 
460 	private void addWorldImpl(shared WorldInfo info, World world) {
461 		if(info is null) info = new shared WorldInfo();
462 		world.info = info;
463 		world.info.group = this.info;
464 		this.info.worlds[world.id] = world.info;
465 		if(this.info.defaultWorld is null) this.info.defaultWorld = world.info;
466 		this._worlds[world.id] = world;
467 		// send packet to the hub
468 		Handler.sharedInstance.send(new HncomAddWorld(info.id, this.info.id, this.info.name, world.dimension, false).autoEncode()); //TODO default?
469 		// set events listener
470 		world.setListener(cast()server.globalListener);
471 	}
472 
473 	/**
474 	 * Removes a world.
475 	 */
476 	public void removeWorld(uint worldId) {
477 		World* world = worldId in this._worlds;
478 		if(world) {
479 			//TODO check whether it can be stopped
480 			// send packet to the hub
481 			Handler.sharedInstance.send(new HncomRemoveWorld(worldId).autoEncode());
482 			//TODO save and delete
483 			world.stop();
484 		}
485 	}
486 	
487 	/*
488 	 * Creates and spawn a player when it comes from another world group,
489 	 * node or from a new connection.
490 	 */
491 	private Player spawnPlayer(shared PlayerInfo info, bool transferred) {
492 
493 		World world = this._worlds[this.info.defaultWorld.id]; //TODO allow spawning in custom world
494 		
495 		//TODO load saved info from file
496 		
497 		Player player = (){
498 			final switch(info.type) {
499 				foreach(type ; TypeTuple!("Bedrock", "Java")) {
500 					case mixin("__" ~ toUpper(type) ~ "__"): {
501 						final switch(info.protocol) {
502 							foreach(protocol ; mixin("Supported" ~ type ~ "Protocols")) {
503 								case protocol: {
504 									mixin("alias ReturnPlayer = " ~ type ~ "PlayerImpl;");
505 									return cast(Player)new ReturnPlayer!protocol(info, world, cast(EntityPosition)world.spawnPoint);
506 								}
507 							}
508 						}
509 					}
510 				}
511 			}
512 		}();
513 		
514 		//TODO if the player is transferred from another world or from the hub, send the change dimension packet or unload every chunk
515 		
516 		// send generic informations that will not change when changing dimension
517 		player.sendJoinPacket();
518 		
519 		// add and send to the list
520 		this.addPlayer(player);
521 		
522 		// register server's commands
523 		foreach(name, command; this.server.commands) {
524 			player.registerCommand(cast()command);
525 		}
526 		
527 		// prepare for spawning (send chunks and rules)
528 		world.preSpawnPlayer(player);
529 		
530 		// announce and spawn to entities
531 		world.afterSpawnPlayer(player, true);
532 		
533 		return player;
534 		
535 	}
536 	
537 }
538 
539 struct AddWorld {
540 
541 	shared CreateImpl create;
542 
543 	static class CreateImpl {
544 
545 		abstract void create(WorldGroup group);
546 
547 	}
548 
549 	static class Create(T:World, E...) : CreateImpl {
550 	
551 		shared WorldInfo info;
552 		E args;
553 
554 		this(shared WorldInfo info, E args) {
555 			this.info = info;
556 			this.args = args;
557 		}
558 
559 		override void create(WorldGroup group) {
560 			group.addWorld!T(info, args);
561 		}
562 
563 	}
564 
565 }
566 
567 struct RemoveWorld {
568 
569 	uint worldId;
570 
571 }
572 
573 // server to world
574 struct AddPlayer {
575 
576 	shared PlayerInfo player;
577 	uint worldId;
578 	bool transferred;
579 
580 }
581 
582 // server to world
583 struct RemovePlayer {
584 
585 	uint playerId;
586 
587 }
588 
589 // server to world
590 struct GamePacket {
591 
592 	uint playerId;
593 	immutable(ubyte)[] payload;
594 
595 }
596 
597 // server to world
598 struct Broadcast {
599 
600 	string message;
601 
602 }
603 
604 // server to world
605 struct UpdateDifficulty {
606 
607 	Difficulty difficulty;
608 
609 }
610 
611 // server to world
612 struct UpdatePlayerGamemode {
613 
614 	uint playerId;
615 	Gamemode gamemode;
616 
617 }
618 
619 // server to world
620 struct UpdatePlayerPermissionLevel {
621 
622 	uint playerId;
623 	PermissionLevel permissionLevel;
624 
625 }
626 
627 // server to world
628 struct Close {}
629 
630 // world to server
631 struct CloseResult {
632 
633 	enum ubyte REMOVED = 0;
634 	enum ubyte PLAYERS_ONLINE = 1;
635 
636 	uint groupId;
637 	ubyte status;
638 
639 }