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/world/world.d, selery/world/world.d)
28  */
29 module selery.world.world;
30 
31 import core.atomic : atomicOp;
32 import core.thread : Thread;
33 
34 import std.algorithm : sort, min, canFind;
35 static import std.concurrency;
36 import std.conv : to;
37 import std.datetime : dur;
38 import std.datetime.stopwatch : StopWatch;
39 import std.math : sin, cos, PI, pow;
40 import std.random : Random, unpredictableSeed, uniform, uniform01, dice;
41 import std.string : replace, toLower, toUpper, join;
42 import std.traits : Parameters;
43 import std.typecons : Tuple;
44 import std.typetuple : TypeTuple;
45 
46 import sel.hncom.about;
47 import sel.hncom.status : AddWorld, RemoveWorld;
48 
49 import selery.about;
50 import selery.block.block : Block, PlacedBlock, Update, Remove, blockInto;
51 import selery.block.blocks : BlockStorage, Blocks;
52 import selery.block.tile : Tile;
53 import selery.command.command : Command;
54 import selery.command.util : WorldCommandSender;
55 import selery.config : Config;
56 import selery.config : Gamemode, Difficulty, Dimension;
57 import selery.entity.entity : Entity;
58 import selery.entity.living : Living;
59 import selery.entity.noai : ItemEntity, Lightning;
60 import selery.event.event : Event, EventListener;
61 import selery.event.world.entity : EntityEvent;
62 import selery.event.world.player : PlayerEvent, PlayerSpawnEvent, PlayerAfterSpawnEvent, PlayerDespawnEvent, PlayerAfterDespawnEvent;
63 import selery.event.world.world : WorldEvent;
64 import selery.item.item : Item;
65 import selery.item.items : ItemStorage, Items;
66 import selery.item.slot : Slot;
67 import selery.lang : Translation;
68 import selery.log : Format, Message;
69 import selery.math.vector;
70 import selery.node.handler : Handler;
71 import selery.node.info : PlayerInfo, WorldInfo;
72 import selery.node.server : NodeServer;
73 import selery.player.bedrock : BedrockPlayerImpl;
74 import selery.player.java : JavaPlayerImpl;
75 import selery.player.player : Player, isPlayer;
76 import selery.plugin : Plugin, loadPluginAttributes, Description;
77 import selery.util.color : Color;
78 import selery.util.util : call;
79 import selery.world.chunk;
80 import selery.world.generator;
81 import selery.world.map : Map;
82 import selery.world.plugin : loadWorld;
83 import selery.world.task : TaskManager;
84 import selery.world.thread;
85 
86 static import sul.blocks;
87 
88 /**
89  * Common properties for a group of worlds.
90  */
91 final class WorldGroup {
92 
93 	Random random;
94 
95 	Player[] players;
96 	Player[uint] playersAA;
97 
98 	Gamemode gamemode;
99 	Difficulty difficulty;
100 
101 	// rules that the client does not need to know about
102 	bool pvp;
103 	bool naturalRegeneration;
104 	bool depleteHunger;
105 	uint randomTickSpeed;
106 	uint viewDistance;
107 
108 	Time time;
109 	Weather weather;
110 
111 	private this(ref Random random) {
112 		this.random = random;
113 	}
114 
115 	public this(ref Random random, const Config.Node config) {
116 		this(random);
117 		this.gamemode = {
118 			switch(config.gamemode) {
119 				default: return Gamemode.survival;
120 				case 1: return Gamemode.creative;
121 				case 2: return Gamemode.adventure;
122 				case 3: return Gamemode.spectator;
123 			}
124 		}();
125 		this.difficulty = {
126 			switch(config.difficulty) {
127 				case 0: return Difficulty.peaceful;
128 				case 1: return Difficulty.easy;
129 				default: return Difficulty.normal;
130 				case 3: return Difficulty.hard;
131 			}
132 		}();
133 		this.depleteHunger = config.depleteHunger;
134 		this.naturalRegeneration = config.naturalRegeneration;
135 		this.pvp = config.pvp;
136 		this.randomTickSpeed = config.randomTickSpeed;
137 		this.viewDistance = config.viewDistance;
138 		//TODO more rules
139 		this.time = new Time(config.doDaylightCycle);
140 		this.weather = new Weather(config.doWeatherCycle);
141 		this.weather.clear();
142 	}
143 
144 	//TODO constructor from world's save file
145 
146 	void tick() {
147 		if(this.time.cycle) {
148 			if(++this.time._time == 24000) {
149 				this.time._time = 0;
150 				this.time.day++;
151 			}
152 		}
153 		if(this.weather.cycle) {
154 			if(--this.weather._time == 0) {
155 				if(this.weather._raining) this.weather.clear();
156 				else this.weather.start();
157 			}
158 		}
159 	}
160 
161 	// updates
162 
163 	void addPlayer(Player player) {
164 		this.players.call!"sendAddList"([player]);
165 		this.players ~= player;
166 		this.playersAA[player.hubId] = player;
167 		player.sendAddList(this.players);
168 	}
169 
170 	void removePlayer(Player player, bool closed) {
171 		foreach(i, p; this.players) {
172 			if(p.hubId == player.hubId) {
173 				this.playersAA.remove(player.hubId);
174 				this.players = this.players[0..i] ~ this.players[i+1..$];
175 				this.players.call!"sendRemoveList"([player]);
176 				if(!closed) player.sendRemoveList(this.players);
177 			}
178 		}
179 	}
180 
181 	void updateDifficulty(Difficulty difficulty) {
182 		this.difficulty = difficulty;
183 		this.players.call!"sendDifficulty"(difficulty);
184 	}
185 
186 	void updateGamemode(Gamemode gamemode) {
187 		this.gamemode = gamemode;
188 		this.players.call!"sendWorldGamemode"(gamemode);
189 	}
190 
191 	private class Time {
192 
193 		bool _cycle;
194 		uint day;
195 		uint _time;
196 
197 		this(bool cycle) {
198 			this._cycle = cycle;
199 		}
200 
201 		@property bool cycle() {
202 			return _cycle;
203 		}
204 
205 		@property bool cycle(bool cycle) {
206 			players.call!"sendDoDaylightCycle"(cycle);
207 			return _cycle = cycle;
208 		}
209 
210 		@property uint time() {
211 			return _time;
212 		}
213 
214 		@property uint time(uint time) {
215 			time %= 24000;
216 			players.call!"sendTime"(time);
217 			return _time = time;
218 		}
219 
220 		alias time this;
221 
222 	}
223 
224 	private class Weather {
225 
226 		public bool cycle;
227 		private bool _raining, _thunderous;
228 		private uint _time;
229 		private uint _intensity;
230 
231 		this(bool cycle) {
232 			this.cycle = cycle;
233 		}
234 
235 		@property bool raining() {
236 			return _raining;
237 		}
238 
239 		@property bool thunderous() {
240 			return _thunderous;
241 		}
242 
243 		@property uint intensity() {
244 			return _intensity;
245 		}
246 
247 		void clear(uint duration) {
248 			_raining = _thunderous = false;
249 			_time = duration;
250 			_intensity = 0;
251 			update();
252 		}
253 
254 		void clear() {
255 			clear(uniform!"[]"(12000u, 180000u, random));
256 		}
257 
258 		void start(uint time, uint intensity, bool thunderous) {
259 			assert(intensity > 0);
260 			_raining = true;
261 			_thunderous = thunderous;
262 			_time = time;
263 			_intensity = intensity;
264 			update();
265 		}
266 
267 		void start(uint time, bool thunderous) {
268 			start(time, uniform!"[]"(1, 4, random), thunderous);
269 		}
270 
271 		void start() {
272 			start(uniform!"[]"(12000u, 24000u, random), !dice(random, .5, .5));
273 		}
274 
275 		private void update() {
276 			players.call!"sendWeather"(_raining, _thunderous, _time, _intensity);
277 		}
278 
279 	}
280 
281 }
282 
283 private shared uint world_count = 0;
284 
285 /**
286  * Basic world.
287  */
288 class World : EventListener!(WorldEvent, EntityEvent, "entity", PlayerEvent, "player") {
289 
290 	public static void startWorld(T:World)(shared NodeServer server, shared WorldInfo info, T world, World parent, bool default_=false) {
291 		world.info = info;
292 		// send world info to the hub
293 		Handler.sharedInstance.send(AddWorld(world.id, world.name, world.dimension, default_, parent is null ? -1 : parent.id).encode());
294 		// update variables and start
295 		world.n_server = server;
296 		world.setListener(cast()server.globalListener);
297 		if(parent is null) {
298 			world.initParent();
299 		} else {
300 			world.initChild();
301 			world.setListener(parent.inheritance);
302 			world.inheritance = parent.inheritance;
303 		}
304 		world._update_state = delegate(int oldState, uint newState){ loadWorld(world, oldState, newState); };
305 		world.updateState(0);
306 		world.start();
307 	}
308 
309 	/+public static void stopWorld(World world, World transferTo) {
310 		if(transferTo !is null) {
311 			void transfer(World from) {
312 				auto c = from.w_players.length;
313 				if(c) {
314 					warning_log(translate(Translation("warning.removingWithPlayers"), world.server.settings.language, [from.name, to!string(c)]));
315 					foreach(player ; from.w_players) {
316 						player.world = transferTo;
317 					}
318 				}
319 				foreach(child ; from.children) {
320 					transfer(child);
321 				}
322 			}
323 			transfer(world);
324 		}
325 		world.stop();
326 	}+/
327 
328 	public immutable uint id;
329 
330 	protected shared WorldInfo info;
331 
332 	private bool _started = false;
333 	private bool _stopped = false;
334 
335 	protected shared NodeServer n_server;
336 
337 	protected Dimension n_dimension = Dimension.overworld;
338 	protected uint n_seed;
339 	protected string n_type;
340 
341 	private shared(WorldInfo)[uint] children_info;
342 
343 	private int _state = -1;
344 	protected void delegate(int, uint) _update_state;
345 
346 	private WorldGroup group;
347 
348 	private World n_parent;
349 	private World[] n_children;
350 	
351 	protected BlockStorage n_blocks;
352 	protected ItemStorage n_items;
353 	
354 	private Random _random;
355 	
356 	private tick_t n_ticks = 0;
357 
358 	protected EventListener!WorldEvent inheritance;
359 	protected TaskManager task_manager;
360 
361 	protected Generator generator;
362 	
363 	public BlockPosition spawnPoint;
364 	
365 	private Chunk[int][int] n_chunks;
366 	public ChunkPosition[] defaultChunks;
367 
368 	private Map[int][int][ubyte] maps;
369 	private Map[ushort] id_maps;
370 
371 	public bool updateBlocks = false;
372 
373 	//TODO move in chunks
374 
375 	private PlacedBlock[] updated_blocks;
376 	private Tile[size_t] updated_tiles;
377 	
378 	private Entity[size_t] w_entities;
379 	private Player[size_t] w_players;
380 
381 	private ScheduledUpdate[] scheduled_updates;
382 
383 	private TaskManager tasks;
384 
385 	private Command[string] commands;
386 	
387 	public this(Generator generator=null, uint seed=unpredictableSeed) {
388 		this.id = atomicOp!"+="(world_count, 1);
389 		this.n_seed = seed;
390 		if(this.n_blocks is null) this.n_blocks = new BlockStorage();
391 		if(this.n_items is null) this.n_items = new ItemStorage();
392 		this._random = Random(this.seed);
393 		this.inheritance = new EventListener!WorldEvent();
394 		this.generator = generator is null ? new Flat(this) : generator;
395 		this.generator.seed = seed;
396 		this.n_type = this.generator.type;
397 		this.spawnPoint = this.generator.spawn;
398 		this.tasks = new TaskManager();
399 	}
400 	
401 	public this(uint seed) {
402 		this(null, seed);
403 	}
404 
405 	protected final void initParent() {
406 		this.group = new WorldGroup(this.random, this.server.config.node);
407 	}
408 
409 	protected final void initChild() {
410 		this.group = this.parent.group;
411 	}
412 
413 	/*
414 	 * Function called when the the world is created.
415 	 * Calls init and orders the default chunks.
416 	 */
417 	protected void start() {
418 		this.initChunks();
419 		this.updated_blocks.length = 0;
420 		sort!"a.x.abs + a.z.abs < b.x.abs + b.z.abs"(this.defaultChunks);
421 		this.updateBlocks = true;
422 	}
423 
424 	/*
425 	 * Initialise chunks.
426 	 */
427 	protected void initChunks() {
428 		
429 		immutable int radius = 5;
430 		//load some chunks as a flat world
431 		foreach(int x ; -radius..radius) {
432 			foreach(int z ; -radius..radius) {
433 				if(!(x == -radius && z == -radius) && !(x == radius-1 && z == radius-1) && !(x == -radius && z == radius-1) && !(x == radius-1 && z == -radius)) {
434 					this.generate(x, z);
435 					this.defaultChunks ~= ChunkPosition(x, z);
436 				}
437 			}
438 		}
439 
440 	}
441 
442 	/*
443 	 * Function called when the world is closed.
444 	 * Saves the resources if they need to be saved and unloads the chunks
445 	 * freeing the memory allocated.
446 	 */
447 	protected void stop() {
448 		foreach(ref chunks ; this.n_chunks) {
449 			foreach(ref chunk ; chunks) {
450 				chunk.unload();
451 			}
452 		}
453 	}
454 
455 	public final pure nothrow @property @safe @nogc shared(NodeServer) server() {
456 		return this.n_server;
457 	}
458 
459 	/**
460 	 * Gets the world's name used for identification and
461 	 * logging purposes.
462 	 * Children have the same name as their parent.
463 	 * Example:
464 	 * ---
465 	 * if(!server.worldsWithName(world.name).canFind(world)) {
466 	 *    log(world.name, " is not managed by the server");
467 	 * }
468 	 * ---
469 	 */
470 	public final pure nothrow @property @safe @nogc immutable(string) name() {
471 		return this.info.name;
472 	}
473 
474 	/**
475 	 * Gets the world's seed used for terrain and randomness
476 	 * generation.
477 	 */
478 	public final pure nothrow @property @safe @nogc const(uint) seed() {
479 		return this.n_seed;
480 	}
481 
482 	/**
483 	 * Gets the world's type as a string.
484 	 * Valid types are "flat" and "default" for both Minecraft and
485 	 * Minecraft: Pocket Edition plus "largeBiomes", "amplified" and
486 	 * "customized" for Minecraft only.
487 	 */
488 	public final pure nothrow @property @safe @nogc const(string) type() {
489 		return this.n_type;
490 	}
491 	
492 	/**
493 	 * Gets the world's dimension as a group of bytes.
494 	 * Example:
495 	 * ---
496 	 * if(world.dimension == Dimension.nether) {
497 	 *    log("world is nether!");
498 	 * }
499 	 * ---
500 	 */
501 	public final pure nothrow @property @safe @nogc Dimension dimension() {
502 		return this.n_dimension;
503 	}
504 
505 	/**
506 	 * Gets/sets the world's gamemode.
507 	 */
508 	public final pure nothrow @property @safe @nogc Gamemode gamemode() {
509 		return this.group.gamemode;
510 	}
511 
512 	/// ditto
513 	public final @property Gamemode gamemode(Gamemode gamemode) {
514 		this.group.updateGamemode(gamemode);
515 		return gamemode;
516 	}
517 
518 	/// ditto
519 	public final @property Gamemode gamemode(int gamemode) {
520 		with(Gamemode) assert(gamemode >= survival && gamemode <= spectator);
521 		return this.gamemode = cast(Gamemode)gamemode;
522 	}
523 
524 	/**
525 	 * Gets/sets the world's difficulty.
526 	 */
527 	public final pure nothrow @property @safe @nogc Difficulty difficulty() {
528 		return this.group.difficulty;
529 	}
530 
531 	/// ditto
532 	public final @property Difficulty difficulty(Difficulty difficulty) {
533 		this.group.updateDifficulty(difficulty);
534 		return difficulty;
535 	}
536 
537 	/// ditto
538 	public final @property Difficulty difficulty(int difficulty) {
539 		assert(difficulty >= Difficulty.peaceful && difficulty <= Difficulty.hard);
540 		return this.difficulty = cast(Difficulty)difficulty;
541 	}
542 
543 	/**
544 	 * Gets/sets whether the pvp (player vs player) is active in the
545 	 * current group of worlds.
546 	 */
547 	public final pure nothrow @property @safe @nogc bool pvp() {
548 		return this.group.pvp;
549 	}
550 
551 	/// ditto
552 	public final pure nothrow @property @safe @nogc bool pvp(bool pvp) {
553 		return this.group.pvp = pvp;
554 	}
555 
556 	/**
557 	 * Gets/sets whether player's health regenerates naturally or not.
558 	 */
559 	public final pure nothrow @property @safe @nogc bool naturalRegeneration() {
560 		return this.group.naturalRegeneration;
561 	}
562 
563 	/// ditto
564 	public final pure nothrow @property @safe @nogc bool naturalRegeneration(bool naturalRegeneration) {
565 		return this.group.naturalRegeneration = naturalRegeneration;
566 	}
567 
568 	/**
569 	 * Gets/sets whether players' hunger is depleted when the world's difficulty
570 	 * is not set to peaceful.
571 	 */
572 	public final pure nothrow @property @safe @nogc bool depleteHunger() {
573 		return this.group.depleteHunger;
574 	}
575 
576 	/// ditto
577 	public final pure nothrow @property @safe @nogc bool depleteHunger(bool depleteHunger) {
578 		return this.group.depleteHunger = depleteHunger;
579 	}
580 
581 	/**
582 	 * Gets the world's time manager.
583 	 * Example:
584 	 * ---
585 	 * log("Time of the day is ", world.time.time);
586 	 * log("Day is ", world.time.day);
587 	 * log("Do daylight cycle? ", world.time.cycle);
588 	 * world.time = 25000;
589 	 * assert(world.time == 1000);
590 	 * world.time = Time.noon;
591 	 * ---
592 	 */
593 	public final pure nothrow @property @safe @nogc auto time() {
594 		return this.group.time;
595 	}
596 
597 	/**
598 	 * Gets the world's weather manager.
599 	 * Example:
600 	 * ---
601 	 * log("is it raining? ", world.weather.raining);
602 	 * log("do weather cycle? ", world.weather.cycle);
603 	 * world.weather.start();
604 	 * world.weather.clear();
605 	 * world.weather.start(24000);
606 	 * assert(world.weather.raining);
607 	 * ---
608 	 */
609 	public final pure nothrow @property @safe @nogc auto weather() {
610 		return this.group.weather;
611 	}
612 
613 	/**
614 	 * Gets/sets the worlds group's default highest view distance for players.
615 	 */
616 	public final pure nothrow @property @safe @nogc uint viewDistance() {
617 		return this.group.viewDistance;
618 	}
619 
620 	/// ditto
621 	public final pure nothrow @property @safe @nogc uint viewDistance(uint viewDistance) {
622 		return this.group.viewDistance = viewDistance;
623 	}
624 
625 	/**
626 	 * Gets/sets the world's random tick speed.
627 	 */
628 	public final pure nothrow @property @safe @nogc uint randomTickSpeed() {
629 		return this.group.randomTickSpeed;
630 	}
631 
632 	/// ditto
633 	public final pure nothrow @property @safe @nogc uint randomTickSpeed(uint randomTickSpeed) {
634 		return this.group.randomTickSpeed = randomTickSpeed;
635 	}
636 	
637 	/*
638 	 * Gets the world's blocks.
639 	 */
640 	public final pure nothrow @property @safe @nogc ref BlockStorage blocks() {
641 		return this.n_blocks;
642 	}
643 	
644 	/*
645 	 * Gets the world's items.
646 	 */
647 	public final pure nothrow @property @safe @nogc ref ItemStorage items() {
648 		return this.n_items;
649 	}
650 	
651 	/**
652 	 * Gets the world's random generator initialised with the
653 	 * world's seed.
654 	 */
655 	public final pure nothrow @property @safe @nogc ref Random random() {
656 		return this._random;
657 	}
658 
659 	/**
660 	 * Gets the world's parent world.
661 	 * Returns: A world instance if the world has a parent, null otherwise
662 	 * Example:
663 	 * ---
664 	 * if(world.parent !is null) {
665 	 *    assert(world.parent.canFind(world));
666 	 * }
667 	 * ---
668 	 */
669 	public final pure nothrow @property @safe @nogc World parent() {
670 		return this.n_parent;
671 	}
672 
673 	/**
674 	 * Gets the world's children.
675 	 * Returns: An array of worlds, empty if the world has no children
676 	 * Example:
677 	 * ---
678 	 * if(world.children.length) {
679 	 *    log(world.name, " has ", world.children.length, " child(ren)");
680 	 * }
681 	 * ---
682 	 */
683 	public final pure nothrow @property @safe @nogc World[] children() {
684 		return this.n_children;
685 	}
686 
687 	/**
688 	 * Checks whether or not the given world is a child of this one.
689 	 * Example:
690 	 * ---
691 	 * if(!overworld.hasChild(nether)) {
692 	 *    overworld.addChild(nether);
693 	 * }
694 	 * ---
695 	 */
696 	public final pure nothrow @safe @nogc bool hasChild(World world) {
697 		foreach(child ; this.n_children) {
698 			if(world.id == child.id) return true;
699 		}
700 		return false;
701 	}
702 
703 	/**
704 	 * Adds a child to the world.
705 	 * A child world is not managed (and ticked) by the server but
706 	 * by its parent. This means that this method should be used instead
707 	 * of server.addWorld.
708 	 * Returns: a new instance of the given world, constructed with the given parameters
709 	 * Example:
710 	 * ---
711 	 * auto overworld = server.addWorld!Overworld();
712 	 * auto nether = overworld.addChild!Nether();
713 	 * ---
714 	 */
715 	public final T addChild(T:World=World, E...)(E args) if(__traits(compiles, new T(args))) {
716 		return this.addChildImpl(new T(args));
717 	}
718 
719 	private World addChildImpl(World world) {
720 		assert(world.parent is null);
721 		world.n_parent = this;
722 		world.n_server = this.server;
723 		world.info = cast(shared)new WorldInfo(world.info.id, world.info.name);
724 		world.info.tid = this.info.tid;
725 		world.info.parent = this.info;
726 		this.info.children[world.id] = world.info;
727 		World.startWorld(this.server, world.info, world, this);
728 		this.n_children ~= world;
729 		return world;
730 	}
731 
732 	/**
733 	 * Removes a child and its children teleporting their players to
734 	 * the current world's spawn point.
735 	 * Returns: true if the given world was a child, false otherwise
736 	 */
737 	public final bool removeChild(World world) {
738 		foreach(i, child; this.n_children) {
739 			if(world.id == child.id) {
740 				this.n_children = this.n_children[0..i] ~ this.n_children[i+1..$];
741 				void unload(World w) {
742 					foreach(player ; w.players) player.teleport(this, cast(EntityPosition)this.spawnPoint);
743 					w.stop(); // unload chunks
744 					foreach(child ; w.children) unload(child);
745 				}
746 				unload(world); // teleport all players in the current world's spawn
747 				this.info.children.remove(world.id);
748 				return true;
749 			}
750 		}
751 		return false;
752 	}
753 
754 	/**
755 	 * Gets the current world's state.
756 	 */
757 	protected final pure nothrow @property @safe @nogc uint currentState() {
758 		return this._state;
759 	}
760 
761 	/**
762 	 * Updates the world's state.
763 	 */
764 	protected final void updateState(uint state) {
765 		if(this._state != state) {
766 			this._update_state(this._state, state);
767 			this._state = state;
768 		}
769 	}
770 
771 	// main loop for main worlds (not children)
772 	public void startMainWorldLoop() {
773 
774 		void tickChildren(World world) {
775 			world.tick();
776 			foreach(child ; world.children) tickChildren(child);
777 		}
778 
779 		StopWatch timer;
780 		ulong duration;
781 
782 		while(!this._stopped) {
783 
784 			timer.start();
785 
786 			// handle server's message (new players, new packets, ...)
787 			this.handleServerPackets();
788 
789 			// tick the group
790 			this.group.tick();
791 
792 			// tick the world and the children
793 			tickChildren(this);
794 
795 			// flush player's packets
796 			foreach(player ; this.group.players) player.flush();
797 
798 			// sleep until next tick
799 			timer.stop();
800 			timer.peek.split!"usecs"(duration);
801 			if(duration < 50_000) {
802 				Thread.sleep(dur!"usecs"(50_000 - duration));
803 			} else {
804 				//TODO server is less than 20 tps!
805 			}
806 			timer.reset();
807 
808 		}
809 
810 		//TODO make sure no players are online
811 
812 		//TODO send RemoveWorld to the hub (children will be removed automatically)
813 
814 		//TODO also stop children
815 		this.stop();
816 
817 		std.concurrency.send(cast()this.server.tid, CloseResult(this.info.id, CloseResult.REMOVED));
818 
819 	}
820 
821 	private void handleServerPackets() {
822 		while(std.concurrency.receiveTimeout(dur!"msecs"(0),
823 			&this.handleAddPlayer,
824 			&this.handleRemovePlayer,
825 			&this.handleGamePacket,
826 			&this.handleBroadcast,
827 			&this.handleUpdateDifficulty,
828 			&this.handleUpdatePlayerGamemode,
829 			&this.handleUpdatePlayerPermissionLevel,
830 			&this.handleClose,
831 		)) {}
832 	}
833 
834 	private void handleAddPlayer(AddPlayer packet) {
835 		//TODO allow spawning in a child
836 		this.spawnPlayer(packet.player, packet.transferred);
837 	}
838 
839 	private void handleRemovePlayer(RemovePlayer packet) {
840 		auto player = packet.playerId in this.group.playersAA;
841 		if(player) {
842 			this.group.removePlayer(*player, false); //TODO whether the player has left the server
843 			(*player).world.despawnPlayer(*player);
844 			(*player).close();
845 		}
846 	}
847 
848 	private void handleGamePacket(GamePacket packet) {
849 		auto player = packet.playerId in this.group.playersAA;
850 		if(player) {
851 			(*player).handle(packet.payload[0], packet.payload[1..$].dup);
852 		}
853 	}
854 
855 	private void handleBroadcast(Broadcast packet) {
856 		this.broadcast(packet.message);
857 	}
858 
859 	private void handleUpdateDifficulty(UpdateDifficulty packet) {
860 		this.difficulty = packet.difficulty;
861 	}
862 
863 	private void handleUpdatePlayerGamemode(UpdatePlayerGamemode packet) {
864 		auto player = packet.playerId in this.group.playersAA;
865 		if(player) {
866 			(*player).gamemode = packet.gamemode;
867 		}
868 	}
869 
870 	private void handleUpdatePlayerPermissionLevel(UpdatePlayerPermissionLevel packet) {
871 		auto player = packet.playerId in this.group.playersAA;
872 		if(player) {
873 			(*player).permissionLevel = packet.permissionLevel;
874 		}
875 	}
876 
877 	private void handleClose(Close packet) {
878 		if(this.group.players.length) {
879 			// cannot close if there are players online in the world or in the children
880 			// the world is not stopped
881 			std.concurrency.send(cast()this.server.tid, CloseResult(this.info.id, CloseResult.PLAYERS_ONLINE));
882 		} else {
883 			// the world will be stopped at the end of the next tick
884 			this._stopped = true;
885 		}
886 	}
887 	
888 	/*
889 	 * Ticks the world and its children.
890 	 * This function should be called by the startMainWorldLoop function
891 	 * (if parent world is null) or by the parent world (if it is not null).
892 	 */
893 	protected void tick() {
894 
895 		this.n_ticks++;
896 
897 		// tasks
898 		if(this.tasks.length) this.tasks.tick(this.ticks);
899 
900 		// random chunk ticks
901 		//if(this.rules.chunkTick) {
902 			foreach(ref c ; this.n_chunks) {
903 				foreach(ref chunk ; c) {
904 					int cx = chunk.x << 4;
905 					int cz = chunk.z << 4;
906 					if(this.weather.thunderous && uniform01!float(this.random) < 1f/100_000) {
907 						ubyte random = uniform!ubyte(this.random);
908 						ubyte x = (random >> 4) & 0xF;
909 						ubyte z = random & 0xF;
910 						auto y = chunk.firstBlock(x, z);
911 						if(y >= 0) this.strike(EntityPosition(chunk.x << 4 | x, y, chunk.z << 4 | z));
912 					}
913 					if(this.weather.raining && uniform01!float(this.random) < .03125f * this.weather.intensity) {
914 						auto xz = chunk.nextSnow;
915 						auto y = chunk.firstBlock(xz.x, xz.z);
916 						if(y > 0) {
917 							immutable temperature = chunk.biomes[xz.z << 4 | xz.x].temperature - (.05f / 30) * min(0, y - 64);
918 							if(temperature <= .95) {
919 								BlockPosition position = BlockPosition(cx | xz.x, y, cz | xz.z);
920 								Block dest = this[position];
921 								if(temperature > .15) {
922 									if(dest == Blocks.cauldron[0..$-1]) {
923 										//TODO add 1 water level to cauldron
924 									}
925 								} else {
926 									if(dest == Blocks.flowingWater0 || dest == Blocks.stillWater0) {
927 										//TODO check block's light level (less than 13)
928 										// change water into ice
929 										this[position] = Blocks.ice;
930 									} else if(dest.fullUpperShape && dest.opacity == 15) {
931 										//TODO check block's light level
932 										// add a snow layer
933 										this[position + [0, 1, 0]] = Blocks.snowLayer0;
934 									}
935 								}
936 							}
937 						}
938 					}
939 					foreach(i, section; chunk.sections) {
940 						if(section.tick) {
941 							immutable y = i << 4;
942 							foreach(j ; 0..this.randomTickSpeed) {
943 								auto random = uniform(0, 4096, this.random);
944 								auto block = section.blocks[random];
945 								if(block && (*block).doRandomTick) {
946 									(*block).onRandomTick(this, BlockPosition(cx | ((random >> 4) & 15), y | ((random >> 8) & 15), cz | (random & 15)));
947 								}
948 							}
949 						}
950 					}
951 				}
952 			}
953 		//}
954 
955 		// scheduled updates
956 		/*if(this.scheduled_updates.length > 0) {
957 			for(uint i=0; i<this.scheduled_updates.length; i++) {
958 				if(this.scheduled_updates[i].time-- == 0) {
959 					this.scheduled_updates[i].block.onScheduledUpdate();
960 					this.scheduled_updates = this.scheduled_updates[0..i] ~ this.scheduled_updates[i+1..$];
961 				}
962 			}
963 		}*/
964 		
965 		// tick the entities
966 		foreach(ref Entity entity ; this.w_entities) {
967 			if(entity.ticking) entity.tick();
968 		}
969 		// and the players
970 		foreach(ref Player player ; this.w_players) {
971 			if(player.ticking) player.tick();
972 		}
973 
974 		// send the updated movements
975 		foreach(ref Player player ; this.w_players) {
976 			player.sendMovements();
977 		}
978 
979 		// set the entities as non-moved
980 		foreach(ref Entity entity ; this.w_entities) {
981 			if(entity.moved) {
982 				entity.moved = false;
983 				entity.oldposition = entity.position;
984 			}
985 			if(entity.motionmoved) entity.motionmoved = false;
986 		}
987 		foreach(ref Player player ; this.w_players) {
988 			if(player.moved) {
989 				player.moved = false;
990 				player.oldposition = player.position;
991 			}
992 			if(player.motionmoved) player.motionmoved = false;
993 		}
994 
995 		// send the updated blocks
996 		if(this.updated_blocks.length > 0) {
997 			this.w_players.call!"sendBlocks"(this.updated_blocks);
998 			this.updated_blocks.length = 0;
999 		}
1000 
1001 		// send the updated tiles
1002 		if(this.updated_tiles.length > 0) {
1003 			foreach(Tile tile ; this.updated_tiles) {
1004 				this.w_players.call!"sendTile"(tile, false);
1005 			}
1006 			//reset
1007 			this.updated_tiles.clear();
1008 		}
1009 
1010 	}
1011 	
1012 	/**
1013 	 * Gets the number of this ticks occurred since the
1014 	 * creation of the world.
1015 	 */
1016 	public final pure nothrow @property @safe @nogc tick_t ticks() {
1017 		return this.n_ticks;
1018 	}
1019 	
1020 	/**
1021 	 * Logs a message into the server's console as this world but without
1022 	 * sending it to the players.
1023 	 */
1024 	public void log(E...)(E args) {
1025 		this.logImpl(Message.convert(args));
1026 	}
1027 	
1028 	protected void logImpl(Message[] messages) {
1029 		this.server.logWorld(messages, this.id); //TODO that doesn't print the world's name in standalone nodes
1030 	}
1031 
1032 	/**
1033 	 * Broadcasts a message (raw or translatable) to every player in the world.
1034 	 */
1035 	public final void broadcast(E...)(E args) {
1036 		static if(E.length == 1 && is(E[0] == Message[])) this.broadcastImpl(args[0]);
1037 		else this.broadcastImpl(Message.convert(args));
1038 	}
1039 
1040 	protected void broadcastImpl(Message[] message) {
1041 		foreach(player ; this.group.players) player.sendMessage(message);
1042 		this.logImpl(message);
1043 	}
1044 
1045 	/**
1046 	 * Broadcasts a tip to the players in the world.
1047 	 */
1048 	public final void broadcastTip(E...)(E args) {
1049 		this.broadcastTipImpl(Message.convert(args));
1050 	}
1051 
1052 	protected void broadcastTipImpl(Message[] message) {
1053 		foreach(player ; this.group.players) player.sendTip(message);
1054 	}
1055 
1056 	/**
1057 	 * Gets the entities spawned in the world.
1058 	 */
1059 	public @property T[] entities(T=Entity, string condition="")() {
1060 		import std.algorithm : filter;
1061 		T[] ret;
1062 		void add(E)(E entity) {
1063 			T a = cast(T)entity;
1064 			if(a) {
1065 				static if(condition.length) {
1066 					mixin("if(" ~ condition ~ ") ret ~= a;");
1067 				} else {
1068 					ret ~= a;
1069 				}
1070 			}
1071 		}
1072 		//TODO improve performances for Player and Entity
1073 		foreach(entity ; this.w_entities) add(entity);
1074 		foreach(player ; this.w_players) add(player);
1075 		return ret;
1076 	}
1077 
1078 	/// ditto
1079 	public @property Entity[] entities(string condition)() {
1080 		return this.entities!(Entity, condition);
1081 	}
1082 
1083 	/**
1084 	 * Gets a list of the players spawned in the world.
1085 	 */
1086 	public @property T[] players(T=Player, string condition="")() if(isPlayer!T) {
1087 		return this.entities!(T, condition);
1088 	}
1089 
1090 	/// ditto
1091 	public @property Player[] players(string condition)() {
1092 		return this.players!(Player, condition);
1093 	}
1094 
1095 	/*
1096 	 * Creates and spawn a player when it comes from another world group,
1097 	 * node or from a new connection.
1098 	 */
1099 	private Player spawnPlayer(shared PlayerInfo info, bool transferred) {
1100 
1101 		info.world = this.info; // set as the main world even when spawned in a child
1102 
1103 		//TODO load saved info from file
1104 
1105 		Player player = (){
1106 			final switch(info.type) {
1107 				foreach(type ; TypeTuple!("Bedrock", "Java")) {
1108 					case mixin("__" ~ toUpper(type) ~ "__"): {
1109 						final switch(info.protocol) {
1110 							foreach(protocol ; mixin("Supported" ~ type ~ "Protocols")) {
1111 								case protocol: {
1112 									mixin("alias ReturnPlayer = " ~ type ~ "PlayerImpl;");
1113 									return cast(Player)new ReturnPlayer!protocol(info, this, cast(EntityPosition)this.spawnPoint);
1114 								}
1115 							}
1116 						}
1117 					}
1118 				}
1119 			}
1120 		}();
1121 
1122 		//TODO if the player is transferred from another world or from the hub, send the change dimension packet or unload every chunk
1123 
1124 		// send generic informations that will not change when changing dimension
1125 		player.sendJoinPacket();
1126 
1127 		// add and send to the list
1128 		this.group.addPlayer(player);
1129 
1130 		// register server's commands
1131 		foreach(name, command; this.server.commands) {
1132 			player.registerCommand(cast()command);
1133 		}
1134 
1135 		// register world's commands
1136 		foreach(command ; this.commands) {
1137 			player.registerCommand(command);
1138 		}
1139 
1140 		// prepare for spawning (send chunks and rules)
1141 		this.preSpawnPlayer(player);
1142 
1143 		// call the spawn event and broadcast message
1144 		auto event = new PlayerSpawnEvent(player);
1145 		this.callEvent(event);
1146 		if(event.announce) this.broadcast(event.message);
1147 
1148 		// spawn to entities
1149 		this.afterSpawnPlayer(player);
1150 
1151 		//TODO call event.after
1152 
1153 		return player;
1154 
1155 	}
1156 
1157 	private void preSpawnPlayer(Player player) {
1158 
1159 		//TODO send packet to the hub with the new world
1160 
1161 		player.spawn = this.spawnPoint; // sends spawn position
1162 		player.move(this.spawnPoint.entityPosition); // send position
1163 
1164 		player.sendResourcePack(); //TODO world's resource pack
1165 		
1166 		//send chunks
1167 		foreach(ChunkPosition pos ; this.defaultChunks) {
1168 			auto chunk = pos in this;
1169 			if(chunk) {
1170 				player.sendChunk(*chunk);
1171 			} else {
1172 				player.sendChunk(this.generate(pos));
1173 			}
1174 			player.loaded_chunks ~= pos;
1175 		}
1176 
1177 		// add world's commands
1178 		foreach(command ; this.commands) {
1179 			player.registerCommand(command);
1180 		}
1181 
1182 		player.sendSettingsPacket();
1183 		player.sendRespawnPacket();
1184 		player.sendInventory();
1185 		player.sendMetadata(player);
1186 
1187 		player.setAsReadyToSpawn();
1188 		player.firstspawn();
1189 
1190 		this.w_players[player.id] = player;
1191 		
1192 		// player may have been teleported during the event, also fixes #8
1193 		player.oldposition = this.spawnPoint.entityPosition;
1194 
1195 	}
1196 
1197 	private void afterSpawnPlayer(Player player) {
1198 
1199 		//TODO let the event choose if spawn or not
1200 		foreach(splayer ; this.w_players) {
1201 			splayer.show(player);
1202 			player.show(splayer);
1203 		}
1204 
1205 		foreach(entity ; this.w_entities) {
1206 			if(entity.shouldSee(player)) entity.show(player);
1207 			/*if(player.shouldSee(entity))*/ player.show(entity); // the player sees  E V E R Y T H I N G
1208 		}
1209 
1210 		atomicOp!"+="(this.info.entities, 1);
1211 		atomicOp!"+="(this.info.players, 1);
1212 
1213 	}
1214 	
1215 	/*
1216 	 * Despawns a player (i.e. on disconnection, on world change, ...).
1217 	 */
1218 	protected final void despawnPlayer(Player player) {
1219 
1220 		//TODO some packet shouldn't be sent when the player is disconnecting or changing dimension
1221 		if(this.w_players.remove(player.id)) {
1222 
1223 			auto event = new PlayerDespawnEvent(player);
1224 			this.callEvent(event);
1225 			if(event.announce) this.broadcast(event.message);
1226 
1227 			foreach(viewer ; player.viewers) {
1228 				viewer.hide(player);
1229 			}
1230 			foreach(watch ; player.watchlist) {
1231 				player.hide(watch/*, false*/); // updating the viewer's watchlist
1232 			}
1233 
1234 			atomicOp!"-="(this.info.entities, 1);
1235 			atomicOp!"-="(this.info.players, 1);
1236 
1237 			this.callEventIfExists!PlayerAfterDespawnEvent(player);
1238 
1239 			// remove world's commands
1240 			foreach(command ; this.commands) {
1241 				player.unregisterCommand(command);
1242 			}
1243 
1244 		}
1245 
1246 	}
1247 
1248 	/**
1249      * Spawns an entity.
1250 	 */
1251 	public final T spawn(T:Entity, E...)(E args) if(!isPlayer!T) {
1252 		T spawned = new T(this, args);
1253 		this.w_entities[spawned.id] = spawned;
1254 		//TODO call the event
1255 		foreach(ref Entity entity ; this.w_entities) {
1256 			if(entity.shouldSee(spawned)) entity.show(spawned);
1257 			if(spawned.shouldSee(entity)) spawned.show(entity);
1258 		}
1259 		foreach(ref Player player ; this.w_players) {
1260 			player.show(spawned); // player sees everything!
1261 			if(spawned.shouldSee(player)) spawned.show(player);
1262 		}
1263 		atomicOp!"+="(this.info.entities, 1);
1264 		return spawned;
1265 	}
1266 
1267 	/+public final void spawn(Entity entity) {
1268 		assert(entity.world == this);
1269 	}+/
1270 
1271 	/**
1272 	 * Despawns an entity.
1273 	 */
1274 	public final void despawn(T:Entity)(T entity) if(!isPlayer!T) {
1275 		if(entity.id in this.w_entities) {
1276 			entity.setAsDespawned();
1277 			this.w_entities.remove(entity.id);
1278 			//TODO call the event
1279 			foreach(ref Entity e ; entity.viewers) {
1280 				e.hide(entity);
1281 			}
1282 			foreach(ref Entity e ; entity.watchlist) {
1283 				entity.hide(e);
1284 			}
1285 			atomicOp!"-="(this.info.entities, 1);
1286 		}
1287 	}
1288 
1289 	/**
1290 	 * Drops an item.
1291 	 */
1292 	public final ItemEntity drop(Slot slot, EntityPosition position, EntityPosition motion) {
1293 		if(!slot.empty) {
1294 			return this.spawn!ItemEntity(position, motion, slot);
1295 		} else {
1296 			return null;
1297 		}
1298 	}
1299 
1300 	/// ditto
1301 	public final ItemEntity drop(Slot slot, EntityPosition position) {
1302 		float f0 = uniform01!float(this.random) * .1f;
1303 		float f1 = uniform01!float(this.random) * PI * 2f;
1304 		return this.drop(slot, position, EntityPosition(-sin(f1) * f0, .2f, cos(f1) * f0));
1305 	}
1306 
1307 	/// ditto
1308 	public final void drop(Block from, BlockPosition position) {
1309 		foreach(slot ; from.drops(this, null, null)) {
1310 			this.drop(slot, position.entityPosition + .5);
1311 		}
1312 	}
1313 
1314 	/**
1315 	 * Stikes a lightning.
1316 	 */
1317 	public final void strike(bool visual=false)(EntityPosition position) {
1318 		this.w_players.call!"sendLightning"(new Lightning(this, position));
1319 		static if(!visual) {
1320 			//TODO do the damages
1321 			//TODO create the fire
1322 		}
1323 	}
1324 
1325 	/// ditto
1326 	public final void strike(Entity entity) {
1327 		this.strike(entity.position);
1328 	}
1329 
1330 	public void explode(bool breakBlocks=true)(EntityPosition position, float power, Living damager=null) {
1331 
1332 		enum float rays = 16;
1333 		enum float half_rays = (rays - 1) / 2;
1334 
1335 		enum float length = .3;
1336 		enum float attenuation = length * .75;
1337 		
1338 		Tuple!(BlockPosition, Block*)[] explodedBlocks;
1339 
1340 		void explodeImpl(EntityPosition ray) {
1341 
1342 			auto pointer = position.dup;
1343 		
1344 			for(double blastForce=uniform!"[]"(.4f, 1.3f, this.random)*power; blastForce>0; blastForce-=attenuation) {
1345 				auto pos = cast(BlockPosition)pointer + [pointer.x < 0 ? 0 : 1, pointer.y < 0 ? 0 : 1, pointer.z < 0 ? 0 : 1];
1346 				//if(pos.y < 0 || pos.y >= 256) break; //TODO use a constant
1347 				auto block = pos in this;
1348 				if(block) {
1349 					blastForce -= ((*block).blastResistance / 5 + length) * length;
1350 					if(blastForce <= 0) break;
1351 					static if(breakBlocks) {
1352 						import std.typecons : tuple;
1353 						explodedBlocks ~= tuple(pos, block);
1354 					}
1355 				}
1356 				pointer += ray;
1357 			}
1358 
1359 		}
1360 
1361 		template Range(float max, E...) {
1362 			static if(E.length >= max) {
1363 				alias Range = E;
1364 			} else {
1365 				alias Range = Range!(max, E, E[$-1] + 1);
1366 			}
1367 		}
1368 
1369 		foreach(x ; Range!(rays, 0)) {
1370 			foreach(y ; Range!(rays, 0)) {
1371 				foreach(z ; Range!(rays, 0)) {
1372 					static if(x == 0 || x == rays - 1 || y == 0 || y == rays - 1 || z == 0 || z == rays - 1) {
1373 
1374 						enum ray = (){
1375 							auto ret = EntityPosition(x, y, z) / half_rays - 1;
1376 							ret.length = length;
1377 							return ret;
1378 						}();
1379 						explodeImpl(ray);
1380 
1381 					}
1382 				}
1383 			}
1384 		}
1385 
1386 		// send packets
1387 		this.players.call!"sendExplosion"(position, power, new Vector3!byte[0]);
1388 
1389 		foreach(exploded ; explodedBlocks) {
1390 			auto block = this[exploded[0]];
1391 			if(block != Blocks.air) {
1392 				this.opIndexAssign!true(Blocks.air, exploded[0]); //TODO use packet's fields
1393 				if(this.random.range(0f, power) <= 1) {
1394 					this.drop(block, exploded[0]);
1395 					//TODO drop experience
1396 				}
1397 			}
1398 		}
1399 
1400 	}
1401 
1402 	public void explode(bool breakBlocks=true)(BlockPosition position, float power, Living damager=null) {
1403 		return this.explode!(breakBlocks)(position.entityPosition, power, damager);
1404 	}
1405 
1406 	/**
1407 	 * Gets an entity from an id.
1408 	 */
1409 	public final @safe Entity entity(uint eid) {
1410 		auto ret = eid in this.w_entities;
1411 		return ret ? *ret : this.player(eid);
1412 	}
1413 
1414 	/**
1415 	 * Gets a player from an id.
1416 	 */
1417 	public final @safe Player player(uint eid) {
1418 		auto ret = eid in this.w_players;
1419 		return ret ? *ret : null;
1420 	}
1421 
1422 	// CHUNKS
1423 
1424 	/**
1425 	 * Gets an associative array with every chunk loaded in
1426 	 * the world.
1427 	 */
1428 	public final pure nothrow @property @safe @nogc Chunk[int][int] chunks() {
1429 		return this.n_chunks;
1430 	}
1431 
1432 	/**
1433 	 * Gets the number of loaded chunks in the world and
1434 	 * its children.
1435 	 */
1436 	public final pure @property @safe @nogc size_t loadedChunks(bool children=false)() {
1437 		size_t ret = 0;
1438 		foreach(chunks ; this.n_chunks) {
1439 			ret += chunks.length;
1440 		}
1441 		static if(children) {
1442 			foreach(child ; this.n_children) {
1443 				ret += child.loadedChunks!true;
1444 			}
1445 		}
1446 		return ret;
1447 	}
1448 
1449 	/**
1450 	 * Gets a chunk.
1451 	 * Returns: the chunk at given position or null if the chunk doesn't exist
1452 	 */
1453 	public final @safe Chunk opIndex(int x, int z) {
1454 		return this.opIndex(ChunkPosition(x, z));
1455 	}
1456 
1457 	/// ditto
1458 	public final @safe Chunk opIndex(ChunkPosition position) {
1459 		auto ret = position in this;
1460 		return ret ? *ret : null;
1461 	}
1462 
1463 	/**
1464 	 * Gets a pointer to the chunk at the given position.
1465 	 * Example:
1466 	 * ---
1467 	 * if(ChunkPosition(100, 100) in world) {
1468 	 *    log("world doesn't have the chunk at 100, 100");
1469 	 * }
1470 	 * ---
1471 	 */
1472 	public final @safe Chunk* opBinaryRight(string op : "in")(ChunkPosition position) {
1473 		auto a = position.x in this.n_chunks;
1474 		return a ? position.z in *a : null;
1475 	}
1476 	
1477 	/**
1478 	 * Sets a chunk.
1479 	 * Example:
1480 	 * ---
1481 	 * auto chunk = new Chunk(world, ChunkPosition(1, 2));
1482 	 * world[] = chunk;
1483 	 * assert(world[1, 2] == chunk);
1484 	 * ---
1485 	 */
1486 	public final Chunk opIndexAssign(Chunk chunk) {
1487 		atomicOp!"+="(this.info.chunks, 1);
1488 		chunk.saveChangedBlocks = true;
1489 		return this.n_chunks[chunk.x][chunk.z] = chunk;
1490 	}
1491 
1492 	/**
1493 	 * Generates and sets a chunk.
1494 	 * Returns: the generated chunk
1495 	 */
1496 	public Chunk generate(ChunkPosition position) {
1497 		return this[] = this.generator.generate(position);
1498 	}
1499 
1500 	/// ditto
1501 	public Chunk generate(int x, int z) {
1502 		return this.generate(ChunkPosition(x, z));
1503 	}
1504 
1505 	/**
1506 	 * Unloads and removes a chunk.
1507 	 * Returns: true if the chunk was removed, false otherwise
1508 	 */
1509 	public bool unload(ChunkPosition position) {
1510 		if(this.defaultChunks.canFind(position)) return false;
1511 		auto chunk = position in this;
1512 		if(chunk) {
1513 			this.n_chunks[position.x].remove(position.z);
1514 			if(this.n_chunks[position.x].length == 0) this.n_chunks.remove(position.x);
1515 			(*chunk).unload();
1516 			return true;
1517 		}
1518 		return false;
1519 	}
1520 
1521 	// Handles the player's chunk request
1522 	public final void playerUpdateRadius(Player player) {
1523 		if(player.viewDistance > this.viewDistance) player.viewDistance = this.viewDistance;
1524 		//TODO allow disallowing auto-sending
1525 		//if(this.rules.chunksAutosending) {
1526 			ChunkPosition pos = player.chunk;
1527 			if(player.last_chunk_position != pos) {
1528 				player.last_chunk_position = pos;
1529 
1530 				ChunkPosition[] new_chunks;
1531 				foreach(int x ; pos.x-player.viewDistance.to!int..pos.x+player.viewDistance.to!int) {
1532 					foreach(int z ; pos.z-player.viewDistance.to!int..pos.z+player.viewDistance.to!int) {
1533 						ChunkPosition c = ChunkPosition(x, z);
1534 						if(distance(c, pos) <= player.viewDistance) {
1535 							new_chunks ~= c;
1536 						}
1537 					}
1538 				}
1539 
1540 				// sort 'em
1541 				sort!"a.x + a.z < b.x + b.z"(new_chunks);
1542 
1543 				// send chunks
1544 				foreach(ChunkPosition cp ; new_chunks) {
1545 					if(!player.loaded_chunks.canFind(cp)) {
1546 						auto chunk = cp in this;
1547 						if(chunk) {
1548 							player.sendChunk(*chunk);
1549 						} else {
1550 							player.sendChunk(this.generate(cp));
1551 						}
1552 					}
1553 				}
1554 
1555 				// unload chunks
1556 				foreach(ref ChunkPosition c ; player.loaded_chunks) {
1557 					if(!new_chunks.canFind(c)) {
1558 						//TODO check if it should be deleted from the world's memory
1559 						//this.unload(this[c]);
1560 						player.unloadChunk(c);
1561 					}
1562 				}
1563 
1564 				// set the new chunks
1565 				player.loaded_chunks = new_chunks;
1566 
1567 			}
1568 		//}
1569 
1570 	}
1571 
1572 	// BLOCKS
1573 
1574 	/**
1575 	 * Gets a block.
1576 	 * Returns: an instance of block, which is never null
1577 	 * Example:
1578 	 * ---
1579 	 * if(world[0, 0, 0] != Blocks.BEDROCK) {
1580 	 *    log("0,0,0 is not bedrock!");
1581 	 * }
1582 	 * ---
1583 	 */
1584 	public Block opIndex(BlockPosition position) {
1585 		auto block = position in this;
1586 		return block ? *block : *this.blocks[0]; // default block (air)
1587 	}
1588 
1589 	/// ditto
1590 	public Block opIndex(int x, uint y, int z) {
1591 		return this.opIndex(BlockPosition(x, y, z));
1592 	}
1593 
1594 	//TODO documentation
1595 	public Block* opBinaryRight(string op : "in")(BlockPosition position) {
1596 		auto chunk = ChunkPosition(position.x >> 4, position.z >> 4) in this;
1597 		return chunk ? (*chunk)[position.x & 15, position.y, position.z & 15] : null;
1598 	}
1599 
1600 	/**
1601 	 * Gets a tile.
1602 	 */
1603 	public @safe T tileAt(T=Tile)(int x, uint y, int z) if(is(T == class) || is(T == interface)) {
1604 		auto chunk = ChunkPosition(x >> 4, z >> 4) in this;
1605 		return chunk ? (*chunk).tileAt!T(BlockPosition(x & 15, y, z & 15)) : null;
1606 	}
1607 
1608 	/// ditto
1609 	public @safe T tileAt(T=Tile)(BlockPosition position) {
1610 		return this.tileAt!T(position.x, position.y, position.z);
1611 	}
1612 
1613 	/**
1614 	 * Sets a block.
1615 	 * Example:
1616 	 * ---
1617 	 * world[0, 55, 12] = Blocks.grass;
1618 	 * world[12, 55, 789] = Blocks.chest; // not a tile!
1619 	 * ---
1620 	 */
1621 	public Block* opIndexAssign(bool sendUpdates=true, T)(T block, BlockPosition position) if(is(T == block_t) || is(T == block_t[]) || is(T == Block*)) {
1622 		auto chunk = ChunkPosition(position.x >> 4, position.z >> 4) in this;
1623 		if(chunk) {
1624 
1625 			Block* ptr = (*chunk)[position.x & 15, position.y, position.z & 15];
1626 			if(ptr) {
1627 				(*ptr).onRemoved(this, position, Remove.unset);
1628 			}
1629 
1630 			static if(is(T == block_t[])) {
1631 				block_t b = block[0];
1632 			} else {
1633 				alias b = block;
1634 			}
1635 
1636 			Block* nb = ((*chunk)[position.x & 15, position.y, position.z & 15] = b);
1637 
1638 			// set as to update
1639 			//TODO move this in the chunk
1640 			static if(sendUpdates) this.updated_blocks ~= PlacedBlock(position, nb ? (*nb).data : sul.blocks.Blocks.air);
1641 
1642 			// call the update function
1643 			if(this.updateBlocks) {
1644 				if(nb) (*nb).onUpdated(this, position, Update.placed);
1645 				this.updateBlock(position + [0, 1, 0]);
1646 				this.updateBlock(position + [1, 0, 0]);
1647 				this.updateBlock(position + [0, 0, 1]);
1648 				this.updateBlock(position - [0, 1, 0]);
1649 				this.updateBlock(position - [1, 0, 0]);
1650 				this.updateBlock(position - [0, 0, 1]);
1651 			}
1652 
1653 			return nb;
1654 
1655 		}
1656 		return null;
1657 	}
1658 
1659 	/// ditto
1660 	public Block* opIndexAssign(T)(T block, int x, uint y, int z) if(is(T == block_t) || is(T == block_t[]) || is(T == Block*)) {
1661 		return this.opIndexAssign(block, BlockPosition(x, y, z));
1662 	}
1663 
1664 	/**
1665 	 * Sets a tile.
1666 	 */
1667 	public void opIndexAssign(T)(T tile, BlockPosition position) if(is(T : Tile) && is(T : Block)) {
1668 		assert(!tile.placed, "This tile has already been placed: " ~ to!string(tile) ~ " at " ~ to!string(position));
1669 		// place the block
1670 		this[position] = tile.id;
1671 		auto chunk = ChunkPosition(position.x >> 4, position.z >> 4) in this;
1672 		if(chunk) {
1673 			// then set it as placed here
1674 			tile.place(this, position);
1675 			// and register the tile in the chunk
1676 			(*chunk).registerTile(tile);
1677 		}
1678 	}
1679 
1680 	/// ditto
1681 	public void opIndexAssign(T)(T tile, int x, uint y, int z) if(is(T : Tile) && is(T : Block)) {
1682 		this.opIndexAssign(tile, BlockPosition(x, y, z));
1683 	}
1684 
1685 	/**
1686 	 * Sets the same block in a rectangualar area.
1687 	 * This method is optimised for building as it uses a cached pointer
1688 	 * instead of getting it every time and it doesn't call any block
1689 	 * update.
1690 	 * Example:
1691 	 * ---
1692 	 * // sets a chunk to stone
1693 	 * world[0..16, 0..$, 0..16] = Blocks.stone;
1694 	 * 
1695 	 * // sets an area to air
1696 	 * world[0..16, 64..128, 0..16] = Blocks.air;
1697 	 * 
1698 	 * // sets a 1-block-high layer only
1699 	 * world[0..16, 64, 0..16] = Blocks.beetroot0;
1700 	 * ---
1701 	 */
1702 	public final void opIndexAssign(block_t b, Slice x, Slice y, Slice z) {
1703 		auto block = b in this.blocks;
1704 		this.updateBlocks = false;
1705 		foreach(px ; x.min..x.max) {
1706 			foreach(py ; y.min..y.max) {
1707 				foreach(pz ; z.min..z.max) {
1708 					this[px, py, pz] = block;
1709 				}
1710 			}
1711 		}
1712 		this.updateBlocks = true;
1713 	}
1714 
1715 	/// ditto
1716 	public final void opIndexAssign(block_t b, int x, Slice y, Slice z) {
1717 		this[x..x+1, y, z] = b;
1718 	}
1719 
1720 	/// ditto
1721 	public final void opIndexAssign(block_t b, Slice x, uint y, Slice z) {
1722 		this[x, y..y+1, z] = b;
1723 	}
1724 
1725 	/// ditto
1726 	public final void opIndexAssign(block_t b, Slice x, Slice y, int z) {
1727 		this[x, y, z..z+1] = b;
1728 	}
1729 
1730 	/// ditto
1731 	public final void opIndexAssign(block_t b, int x, uint y, Slice z) {
1732 		this[x..x+1, y..y+1, z] = b;
1733 	}
1734 
1735 	/// ditto
1736 	public final void opIndexAssign(block_t b, int x, Slice y, int z) {
1737 		this[x..x+1, y, z..z+1] = b;
1738 	}
1739 
1740 	/// ditto
1741 	public final void opIndexAssign(block_t b, Slice x, uint y, int z) {
1742 		this[x, y..y+1, z..z+1] = b;
1743 	}
1744 
1745 	public size_t replace(block_t from, block_t to) {
1746 		return this.replace(from in this.blocks, to in this.blocks);
1747 	}
1748 
1749 	public size_t replace(Block* from, Block* to) {
1750 		size_t ret = 0;
1751 		foreach(cc ; this.n_chunks) {
1752 			foreach(c ; cc) {
1753 				ret += this.replaceImpl(c, from, to);
1754 			}
1755 		}
1756 		return ret;
1757 	}
1758 
1759 	public size_t replace(block_t from, block_t to, BlockPosition fromp, BlockPosition top) {
1760 		if(fromp.x < top.x && fromp.y < top.y && fromp.z < top.z) {
1761 			//TODO
1762 			return 0;
1763 		} else {
1764 			return 0;
1765 		}
1766 	}
1767 
1768 	protected size_t replaceImpl(ref Chunk chunk, Block* from, Block* to) {
1769 		//TODO
1770 		return 0;
1771 	}
1772 
1773 	/// function called by a tile when its data is updated
1774 	public final void updateTile(Tile tile, BlockPosition position) {
1775 		this.updated_tiles[tile.tid] = tile;
1776 	}
1777 
1778 	protected final void updateBlock(BlockPosition position) {
1779 		auto block = position in this;
1780 		if(block) (*block).onUpdated(this, position, Update.nearestChanged);
1781 	}
1782 
1783 	public @safe Slice opSlice(size_t pos)(int min, int max) {
1784 		return Slice(min, max);
1785 	}
1786 
1787 	/// schedules a block update
1788 	public @safe void scheduleBlockUpdate(Block block, uint time) {
1789 		/*if(this.rules.scheduledTicks) {
1790 			this.scheduled_updates ~= ScheduledUpdate(block, time);
1791 		}*/
1792 	}
1793 
1794 	/**
1795 	 * Registers a command.
1796 	 */
1797 	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) {
1798 		command = command.toLower;
1799 		if(command !in this.commands) this.commands[command] = new Command(command, description, aliases, permissionLevel, permissions, hidden);
1800 		auto ptr = command in this.commands;
1801 		(*ptr).add!func(del, implemented);
1802 	}
1803 
1804 	/**
1805 	 * Unregisters a command.
1806 	 */
1807 	public void unregisterCommand(string command) {
1808 		this.commands.remove(command);
1809 	}
1810 
1811 	/**
1812 	 * Registers a task.
1813 	 * Params:
1814 	 *		task = a delegate of a function that will be called every interval
1815 	 *		interval = number of ticks indicating between the calls
1816 	 *		repeat = number of times to repeat the task
1817 	 * Returns:
1818 	 * 		the new task id that can be used to remove the task
1819 	 */
1820 	public @safe size_t addTask(void delegate() task, size_t interval, size_t repeat=size_t.max) {
1821 		return this.tasks.add(task, interval, repeat, this.ticks);
1822 	}
1823 	
1824 	/// ditto
1825 	alias addTask schedule;
1826 	
1827 	/**
1828 	 * Executes a task one time after the given ticks.
1829 	 */
1830 	public @safe size_t delay(void delegate() task, size_t timeout) {
1831 		return this.addTask(task, timeout, 1);
1832 	}
1833 	
1834 	/**
1835 	 * Removes a task using the task's delegate or the id returned
1836 	 * by the addTask function.
1837 	 */
1838 	public @safe void removeTask(void delegate() task) {
1839 		this.tasks.remove(task);
1840 	}
1841 	
1842 	/// ditto
1843 	public @safe void removeTask(size_t tid) {
1844 		this.tasks.remove(tid);
1845 	}
1846 
1847 	public override @safe bool opEquals(Object o) {
1848 		if(cast(World)o) return this.id == (cast(World)o).id;
1849 		else return false;
1850 	}
1851 
1852 	/** Grows a tree in the given world. */
1853 	public static void growTree(World world, BlockPosition position, ushort[] trunk=Blocks.oakWood, ushort leaves=Blocks.oakLeavesDecay) {
1854 		uint height = uniform(3u, 6u, world.random);
1855 		foreach(uint i ; 0..height) {
1856 			world[position + [0, i, 0]] = trunk[0];
1857 		}
1858 
1859 		foreach(uint i ; 0..2) {
1860 			foreach(int x ; -2..3) {
1861 				foreach(int z ; -2..3) {
1862 					world[position + [x, height + i, z]] = leaves;
1863 				}
1864 			}
1865 		}
1866 		foreach(int x ; -1..2) {
1867 			foreach(int z ; -1..2) {
1868 				world[position + [x, height + 2, z]] = leaves;
1869 			}
1870 		}
1871 		world[position + [0, height + 3, 0]] = leaves;
1872 		world[position + [-1, height + 3, 0]] = leaves;
1873 		world[position + [1, height + 3, 0]] = leaves;
1874 		world[position + [0, height + 3, -1]] = leaves;
1875 		world[position + [0, height + 3, 1]] = leaves;
1876 	}
1877 
1878 	public override string toString() {
1879 		return typeid(this).to!string ~ "(" ~ to!string(this.id) ~ ", " ~ this.name ~ ", " ~ to!string(this.n_children) ~ ")";
1880 	}
1881 
1882 }
1883 
1884 private alias Slice = Tuple!(int, "min", int, "max");
1885 
1886 private struct ScheduledUpdate {
1887 
1888 	public Block block;
1889 	public uint time;
1890 
1891 	public @safe @nogc this(ref Block block, uint time) {
1892 		this.block = block;
1893 		this.time = time;
1894 	}
1895 
1896 }
1897 
1898 enum Time : uint {
1899 
1900 	day = 1000,
1901 	night = 13000,
1902 	midnight = 18000,
1903 	noon = 6000,
1904 	sunrise = 23000,
1905 	sunset = 12000,
1906 
1907 }