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/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.format : Format;
47 
48 import selery.about;
49 import selery.block.block : Block, PlacedBlock, Update, Remove, blockInto;
50 import selery.block.blocks : BlockStorage, Blocks;
51 import selery.block.tile : Tile;
52 import selery.command.command : Command;
53 import selery.command.util : WorldCommandSender;
54 import selery.config : Config;
55 import selery.config : Gamemode, Difficulty, Dimension;
56 import selery.entity.entity : Entity;
57 import selery.entity.living : Living;
58 import selery.entity.noai : ItemEntity, Lightning;
59 import selery.event.event : Event, EventListener;
60 import selery.event.world.entity : EntityEvent;
61 import selery.event.world.player : PlayerEvent, PlayerSpawnEvent, PlayerAfterSpawnEvent, PlayerDespawnEvent, PlayerAfterDespawnEvent;
62 import selery.event.world.world : WorldEvent;
63 import selery.item.item : Item;
64 import selery.item.items : ItemStorage, Items;
65 import selery.item.slot : Slot;
66 import selery.lang : Translation;
67 import selery.log : Message;
68 import selery.math.vector;
69 import selery.node.server : NodeServer;
70 import selery.player.bedrock : BedrockPlayerImpl;
71 import selery.player.java : JavaPlayerImpl;
72 import selery.player.player : PlayerInfo, Player, isPlayer;
73 import selery.plugin : Plugin, loadPluginAttributes, Description;
74 import selery.util.color : Color;
75 import selery.util.util : call;
76 import selery.world.chunk;
77 import selery.world.generator;
78 import selery.world.group;
79 import selery.world.map : Map;
80 import selery.world.task : TaskManager;
81 
82 static import sul.blocks;
83 
84 private shared uint _id;
85 
86 final class WorldInfo {
87 
88 	public immutable uint id;
89 
90 	public shared GroupInfo group;
91 
92 	public size_t entities;
93 
94 	public size_t players;
95 
96 	public size_t chunks;
97 
98 	public shared this() {
99 		this.id = atomicOp!"+="(_id, 1);
100 	}
101 
102 }
103 
104 /**
105  * Basic world.
106  */
107 class World : EventListener!(WorldEvent, EntityEvent, "entity", PlayerEvent, "player") {
108 
109 	public shared WorldInfo info;
110 
111 	private bool _started = false;
112 	private bool _stopped = false;
113 
114 	protected Dimension n_dimension = Dimension.overworld;
115 	protected uint n_seed;
116 	protected string n_type;
117 
118 	private int _state = -1;
119 	public void delegate(int, uint) _update_state;
120 
121 	private WorldGroup group;
122 	
123 	protected BlockStorage n_blocks;
124 	protected ItemStorage n_items;
125 	
126 	private Random _random;
127 	
128 	private tick_t n_ticks = 0;
129 
130 	protected Generator generator;
131 	
132 	public BlockPosition spawnPoint;
133 	
134 	private Chunk[int][int] n_chunks;
135 	public ChunkPosition[] defaultChunks;
136 
137 	private Map[int][int][ubyte] maps;
138 	private Map[ushort] id_maps;
139 
140 	public bool updateBlocks = false;
141 
142 	//TODO move in chunks
143 
144 	private PlacedBlock[] updated_blocks;
145 	private Tile[size_t] updated_tiles;
146 	
147 	private Entity[size_t] w_entities;
148 	private Player[size_t] w_players;
149 
150 	private ScheduledUpdate[] scheduled_updates;
151 
152 	private TaskManager tasks;
153 
154 	private Command[string] commands;
155 	
156 	public this(Generator generator=null, uint seed=unpredictableSeed) {
157 		this.info = new shared WorldInfo();
158 		this.n_seed = seed;
159 		if(this.n_blocks is null) this.n_blocks = new BlockStorage();
160 		if(this.n_items is null) this.n_items = new ItemStorage();
161 		this._random = Random(this.seed);
162 		this.generator = generator is null ? new Flat(this) : generator;
163 		this.generator.seed = seed;
164 		this.n_type = this.generator.type;
165 		this.spawnPoint = this.generator.spawn;
166 		this.tasks = new TaskManager();
167 	}
168 	
169 	public this(uint seed) {
170 		this(null, seed);
171 	}
172 
173 	public final pure nothrow @property @safe @nogc uint id() {
174 		return this.info.id;
175 	}
176 
177 	/*
178 	 * Function called when the the world is created.
179 	 * Calls init and orders the default chunks.
180 	 */
181 	public void start(WorldGroup group) {
182 		this.group = group;
183 		this.initChunks();
184 		this.updated_blocks.length = 0;
185 		sort!"a.x.abs + a.z.abs < b.x.abs + b.z.abs"(this.defaultChunks);
186 		this.updateBlocks = true;
187 	}
188 
189 	/*
190 	 * Initialise chunks.
191 	 */
192 	protected void initChunks() {
193 		
194 		immutable int radius = 5;
195 		//load some chunks as a flat world
196 		foreach(int x ; -radius..radius) {
197 			foreach(int z ; -radius..radius) {
198 				if(!(x == -radius && z == -radius) && !(x == radius-1 && z == radius-1) && !(x == -radius && z == radius-1) && !(x == radius-1 && z == -radius)) {
199 					this.generate(x, z);
200 					this.defaultChunks ~= ChunkPosition(x, z);
201 				}
202 			}
203 		}
204 
205 	}
206 
207 	/*
208 	 * Function called when the world is closed.
209 	 * Saves the resources if they need to be saved and unloads the chunks
210 	 * freeing the memory allocated.
211 	 */
212 	public void stop() {
213 		foreach(ref chunks ; this.n_chunks) {
214 			foreach(ref chunk ; chunks) {
215 				chunk.unload();
216 			}
217 		}
218 	}
219 
220 	public final pure nothrow @property @safe @nogc shared(NodeServer) server() {
221 		return this.group.server;
222 	}
223 
224 	/**
225 	 * Gets the world's seed used for terrain and randomness
226 	 * generation.
227 	 */
228 	public final pure nothrow @property @safe @nogc const(uint) seed() {
229 		return this.n_seed;
230 	}
231 
232 	/**
233 	 * Gets the world's type as a string.
234 	 * Valid types are "flat" and "default" for both Minecraft and
235 	 * Minecraft: Pocket Edition plus "largeBiomes", "amplified" and
236 	 * "customized" for Minecraft only.
237 	 */
238 	public final pure nothrow @property @safe @nogc const(string) type() {
239 		return this.n_type;
240 	}
241 	
242 	/**
243 	 * Gets the world's dimension as a group of bytes.
244 	 * Example:
245 	 * ---
246 	 * if(world.dimension == Dimension.nether) {
247 	 *    log("world is nether!");
248 	 * }
249 	 * ---
250 	 */
251 	public final pure nothrow @property @safe @nogc Dimension dimension() {
252 		return this.n_dimension;
253 	}
254 
255 	/**
256 	 * Gets/sets the world's gamemode.
257 	 */
258 	public final pure nothrow @property @safe @nogc Gamemode gamemode() {
259 		return this.group.gamemode;
260 	}
261 
262 	/// ditto
263 	public final @property Gamemode gamemode(Gamemode gamemode) {
264 		this.group.updateGamemode(gamemode);
265 		return gamemode;
266 	}
267 
268 	/**
269 	 * Gets/sets the world's difficulty.
270 	 */
271 	public final pure nothrow @property @safe @nogc Difficulty difficulty() {
272 		return this.group.difficulty;
273 	}
274 
275 	/// ditto
276 	public final @property Difficulty difficulty(Difficulty difficulty) {
277 		this.group.updateDifficulty(difficulty);
278 		return difficulty;
279 	}
280 
281 	/**
282 	 * Gets/sets whether the pvp (player vs player) is active in the
283 	 * current group of worlds.
284 	 */
285 	public final pure nothrow @property @safe @nogc bool pvp() {
286 		return this.group.pvp;
287 	}
288 
289 	/// ditto
290 	public final pure nothrow @property @safe @nogc bool pvp(bool pvp) {
291 		return this.group.pvp = pvp;
292 	}
293 
294 	/**
295 	 * Gets/sets whether player's health regenerates naturally or not.
296 	 */
297 	public final pure nothrow @property @safe @nogc bool naturalRegeneration() {
298 		return this.group.naturalRegeneration;
299 	}
300 
301 	/// ditto
302 	public final pure nothrow @property @safe @nogc bool naturalRegeneration(bool naturalRegeneration) {
303 		return this.group.naturalRegeneration = naturalRegeneration;
304 	}
305 
306 	/**
307 	 * Gets/sets whether players' hunger is depleted when the world's difficulty
308 	 * is not set to peaceful.
309 	 */
310 	public final pure nothrow @property @safe @nogc bool depleteHunger() {
311 		return this.group.depleteHunger;
312 	}
313 
314 	/// ditto
315 	public final pure nothrow @property @safe @nogc bool depleteHunger(bool depleteHunger) {
316 		return this.group.depleteHunger = depleteHunger;
317 	}
318 
319 	/**
320 	 * Gets the world's time manager.
321 	 * Example:
322 	 * ---
323 	 * log("Time of the day is ", world.time.time);
324 	 * log("Day is ", world.time.day);
325 	 * log("Do daylight cycle? ", world.time.cycle);
326 	 * world.time = 25000;
327 	 * assert(world.time == 1000);
328 	 * world.time = Time.noon;
329 	 * ---
330 	 */
331 	public final pure nothrow @property @safe @nogc auto time() {
332 		return this.group.time;
333 	}
334 
335 	/**
336 	 * Gets the world's weather manager.
337 	 * Example:
338 	 * ---
339 	 * log("is it raining? ", world.weather.raining);
340 	 * log("do weather cycle? ", world.weather.cycle);
341 	 * world.weather.start();
342 	 * world.weather.clear();
343 	 * world.weather.start(24000);
344 	 * assert(world.weather.raining);
345 	 * ---
346 	 */
347 	public final pure nothrow @property @safe @nogc auto weather() {
348 		return this.group.weather;
349 	}
350 
351 	/**
352 	 * Gets/sets the worlds group's default highest view distance for players.
353 	 */
354 	public final pure nothrow @property @safe @nogc uint viewDistance() {
355 		return this.group.viewDistance;
356 	}
357 
358 	/// ditto
359 	public final pure nothrow @property @safe @nogc uint viewDistance(uint viewDistance) {
360 		return this.group.viewDistance = viewDistance;
361 	}
362 
363 	/**
364 	 * Gets/sets the world's random tick speed.
365 	 */
366 	public final pure nothrow @property @safe @nogc uint randomTickSpeed() {
367 		return this.group.randomTickSpeed;
368 	}
369 
370 	/// ditto
371 	public final pure nothrow @property @safe @nogc uint randomTickSpeed(uint randomTickSpeed) {
372 		return this.group.randomTickSpeed = randomTickSpeed;
373 	}
374 	
375 	/*
376 	 * Gets the world's blocks.
377 	 */
378 	public final pure nothrow @property @safe @nogc ref BlockStorage blocks() {
379 		return this.n_blocks;
380 	}
381 	
382 	/*
383 	 * Gets the world's items.
384 	 */
385 	public final pure nothrow @property @safe @nogc ref ItemStorage items() {
386 		return this.n_items;
387 	}
388 	
389 	/**
390 	 * Gets the world's random generator initialised with the
391 	 * world's seed.
392 	 */
393 	public final pure nothrow @property @safe @nogc ref Random random() {
394 		return this._random;
395 	}
396 
397 	/**
398 	 * Gets the current world's state.
399 	 */
400 	public final pure nothrow @property @safe @nogc uint currentState() {
401 		return this._state;
402 	}
403 
404 	/*
405 	 * Updates the world's state.
406 	 */
407 	public final void updateState(uint state) {
408 		if(this._state != state) {
409 			this._update_state(this._state, state);
410 			this._state = state;
411 		}
412 	}
413 	
414 	/*
415 	 * Ticks the world and its children.
416 	 * This function should be called by the startMainWorldLoop function
417 	 * (if parent world is null) or by the parent world (if it is not null).
418 	 */
419 	public void tick() {
420 
421 		this.n_ticks++;
422 
423 		// tasks
424 		if(this.tasks.length) this.tasks.tick(this.ticks);
425 
426 		// random chunk ticks
427 		//if(this.rules.chunkTick) {
428 			foreach(ref c ; this.n_chunks) {
429 				foreach(ref chunk ; c) {
430 					int cx = chunk.x << 4;
431 					int cz = chunk.z << 4;
432 					if(this.weather.thunderous && uniform01!float(this.random) < 1f/100_000) {
433 						ubyte random = uniform!ubyte(this.random);
434 						ubyte x = (random >> 4) & 0xF;
435 						ubyte z = random & 0xF;
436 						auto y = chunk.firstBlock(x, z);
437 						if(y >= 0) this.strike(EntityPosition(chunk.x << 4 | x, y, chunk.z << 4 | z));
438 					}
439 					if(this.weather.raining && uniform01!float(this.random) < .03125f * this.weather.intensity) {
440 						auto xz = chunk.nextSnow;
441 						auto y = chunk.firstBlock(xz.x, xz.z);
442 						if(y > 0) {
443 							immutable temperature = chunk.biomes[xz.z << 4 | xz.x].temperature - (.05f / 30) * min(0, y - 64);
444 							if(temperature <= .95) {
445 								BlockPosition position = BlockPosition(cx | xz.x, y, cz | xz.z);
446 								Block dest = this[position];
447 								if(temperature > .15) {
448 									if(dest == Blocks.cauldron[0..$-1]) {
449 										//TODO add 1 water level to cauldron
450 									}
451 								} else {
452 									if(dest == Blocks.flowingWater0 || dest == Blocks.stillWater0) {
453 										//TODO check block's light level (less than 13)
454 										// change water into ice
455 										this[position] = Blocks.ice;
456 									} else if(dest.fullUpperShape && dest.opacity == 15) {
457 										//TODO check block's light level
458 										// add a snow layer
459 										this[position + [0, 1, 0]] = Blocks.snowLayer0;
460 									}
461 								}
462 							}
463 						}
464 					}
465 					foreach(i, section; chunk.sections) {
466 						if(section.tick) {
467 							immutable y = i << 4;
468 							foreach(j ; 0..this.randomTickSpeed) {
469 								auto random = uniform(0, 4096, this.random);
470 								auto block = section.blocks[random];
471 								if(block && (*block).doRandomTick) {
472 									(*block).onRandomTick(this, BlockPosition(cx | ((random >> 4) & 15), y | ((random >> 8) & 15), cz | (random & 15)));
473 								}
474 							}
475 						}
476 					}
477 				}
478 			}
479 		//}
480 
481 		// scheduled updates
482 		/*if(this.scheduled_updates.length > 0) {
483 			for(uint i=0; i<this.scheduled_updates.length; i++) {
484 				if(this.scheduled_updates[i].time-- == 0) {
485 					this.scheduled_updates[i].block.onScheduledUpdate();
486 					this.scheduled_updates = this.scheduled_updates[0..i] ~ this.scheduled_updates[i+1..$];
487 				}
488 			}
489 		}*/
490 		
491 		// tick the entities
492 		foreach(ref Entity entity ; this.w_entities) {
493 			if(entity.ticking) entity.tick();
494 		}
495 		// and the players
496 		foreach(ref Player player ; this.w_players) {
497 			if(player.ticking) player.tick();
498 		}
499 
500 		// send the updated movements
501 		foreach(ref Player player ; this.w_players) {
502 			player.sendMovements();
503 		}
504 
505 		// set the entities as non-moved
506 		foreach(ref Entity entity ; this.w_entities) {
507 			if(entity.moved) {
508 				entity.moved = false;
509 				entity.oldposition = entity.position;
510 			}
511 			if(entity.motionmoved) entity.motionmoved = false;
512 		}
513 		foreach(ref Player player ; this.w_players) {
514 			if(player.moved) {
515 				player.moved = false;
516 				player.oldposition = player.position;
517 			}
518 			if(player.motionmoved) player.motionmoved = false;
519 		}
520 
521 		// send the updated blocks
522 		if(this.updated_blocks.length > 0) {
523 			this.w_players.call!"sendBlocks"(this.updated_blocks);
524 			this.updated_blocks.length = 0;
525 		}
526 
527 		// send the updated tiles
528 		if(this.updated_tiles.length > 0) {
529 			foreach(Tile tile ; this.updated_tiles) {
530 				this.w_players.call!"sendTile"(tile, false);
531 			}
532 			//reset
533 			this.updated_tiles.clear();
534 		}
535 
536 	}
537 	
538 	/**
539 	 * Gets the number of this ticks occurred since the
540 	 * creation of the world.
541 	 */
542 	public final pure nothrow @property @safe @nogc tick_t ticks() {
543 		return this.n_ticks;
544 	}
545 	
546 	/**
547 	 * Logs a message into the server's console as this world but without
548 	 * sending it to the players.
549 	 */
550 	public void log(E...)(E args) {
551 		this.logImpl(Message.convert(args));
552 	}
553 	
554 	protected void logImpl(Message[] messages) {
555 		this.server.logWorld(messages, this.id); //TODO that doesn't print the world's name in standalone nodes
556 	}
557 
558 	/**
559 	 * Broadcasts a message (raw or translatable) to every player in the world.
560 	 * To broadcast a message to every player in the group, use `group.broadcast`.
561 	 */
562 	public void broadcast(E...)(E args) {
563 		static if(E.length == 1 && is(E[0] == Message[])) this.broadcastImpl(args[0]);
564 		else this.broadcastImpl(Message.convert(args));
565 	}
566 
567 	protected void broadcastImpl(Message[] message) {
568 		foreach(player ; this.players) player.sendMessage(message);
569 		this.logImpl(message);
570 	}
571 
572 	/**
573 	 * Broadcasts a tip to the players in the world.
574 	 */
575 	public final void broadcastTip(E...)(E args) {
576 		this.broadcastTipImpl(Message.convert(args));
577 	}
578 
579 	protected void broadcastTipImpl(Message[] message) {
580 		foreach(player ; this.players) player.sendTip(message);
581 	}
582 
583 	/**
584 	 * Gets the entities spawned in the world.
585 	 */
586 	public @property T[] entities(T=Entity, string condition="")() {
587 		import std.algorithm : filter;
588 		T[] ret;
589 		void add(E)(E entity) {
590 			T a = cast(T)entity;
591 			if(a) {
592 				static if(condition.length) {
593 					mixin("if(" ~ condition ~ ") ret ~= a;");
594 				} else {
595 					ret ~= a;
596 				}
597 			}
598 		}
599 		//TODO improve performances for Player and Entity
600 		foreach(entity ; this.w_entities) add(entity);
601 		foreach(player ; this.w_players) add(player);
602 		return ret;
603 	}
604 
605 	/// ditto
606 	public @property Entity[] entities(string condition)() {
607 		return this.entities!(Entity, condition);
608 	}
609 
610 	/**
611 	 * Gets a list of the players spawned in the world.
612 	 */
613 	public @property T[] players(T=Player, string condition="")() if(isPlayer!T) {
614 		return this.entities!(T, condition);
615 	}
616 
617 	/// ditto
618 	public @property Player[] players(string condition)() {
619 		return this.players!(Player, condition);
620 	}
621 
622 	/*
623 	 * Prepares a player for spawning.
624 	 */
625 	public void preSpawnPlayer(Player player) {
626 
627 		player.spawn = this.spawnPoint; // send spawn position
628 		player.move(this.spawnPoint.entityPosition); // send position
629 
630 		player.sendResourcePack(); //TODO world's resource pack
631 		
632 		//send chunks
633 		foreach(ChunkPosition pos ; this.defaultChunks) {
634 			auto chunk = pos in this;
635 			if(chunk) {
636 				player.sendChunk(*chunk);
637 			} else {
638 				player.sendChunk(this.generate(pos));
639 			}
640 			player.loaded_chunks ~= pos;
641 		}
642 
643 		// add world's commands
644 		foreach(command ; this.commands) {
645 			player.registerCommand(command);
646 		}
647 
648 		player.sendSettingsPacket();
649 		player.sendRespawnPacket();
650 		player.sendInventory();
651 		player.sendMetadata(player);
652 
653 		player.setAsReadyToSpawn();
654 		player.firstspawn();
655 
656 		this.w_players[player.id] = player;
657 		
658 		// player may have been teleported during the event, also fixes #8
659 		player.oldposition = this.spawnPoint.entityPosition;
660 
661 	}
662 
663 	/*
664 	 * Spawns and optionally announce a player.
665 	 */
666 	public void afterSpawnPlayer(Player player, bool announce) {
667 
668 		// call the spawn event and broadcast message
669 		auto event = new PlayerSpawnEvent(player, announce);
670 		this.callEvent(event);
671 		if(event.announce) this.group.broadcast(event.message);
672 
673 		//TODO let the event choose if spawn or not
674 		foreach(splayer ; this.w_players) {
675 			splayer.show(player);
676 			player.show(splayer);
677 		}
678 
679 		foreach(entity ; this.w_entities) {
680 			if(entity.shouldSee(player)) entity.show(player);
681 			/*if(player.shouldSee(entity))*/ player.show(entity); // the player sees  E V E R Y T H I N G
682 		}
683 
684 		atomicOp!"+="(this.info.entities, 1);
685 		atomicOp!"+="(this.info.players, 1);
686 
687 	}
688 	
689 	/*
690 	 * Despawns a player (i.e. on disconnection, on world change, ...).
691 	 */
692 	public void despawnPlayer(Player player) {
693 
694 		//TODO some packet shouldn't be sent when the player is disconnecting or changing dimension
695 		if(this.w_players.remove(player.id)) {
696 
697 			auto event = new PlayerDespawnEvent(player, true); //TODO do not announce when moved to another world in the same group
698 			this.callEvent(event);
699 			if(event.announce) this.group.broadcast(event.message);
700 
701 			foreach(viewer ; player.viewers) {
702 				viewer.hide(player);
703 			}
704 			foreach(watch ; player.watchlist) {
705 				player.hide(watch/*, false*/); // updating the viewer's watchlist
706 			}
707 
708 			atomicOp!"-="(this.info.entities, 1);
709 			atomicOp!"-="(this.info.players, 1);
710 
711 			this.callEventIfExists!PlayerAfterDespawnEvent(player);
712 
713 			// remove world's commands
714 			foreach(command ; this.commands) {
715 				player.unregisterCommand(command);
716 			}
717 
718 		}
719 
720 	}
721 
722 	/**
723      * Spawns an entity.
724 	 */
725 	public final T spawn(T:Entity, E...)(E args) if(!isPlayer!T) {
726 		T spawned = new T(this, args);
727 		this.w_entities[spawned.id] = spawned;
728 		//TODO call the event
729 		foreach(ref Entity entity ; this.w_entities) {
730 			if(entity.shouldSee(spawned)) entity.show(spawned);
731 			if(spawned.shouldSee(entity)) spawned.show(entity);
732 		}
733 		foreach(ref Player player ; this.w_players) {
734 			player.show(spawned); // player sees everything!
735 			if(spawned.shouldSee(player)) spawned.show(player);
736 		}
737 		atomicOp!"+="(this.info.entities, 1);
738 		return spawned;
739 	}
740 
741 	/+public final void spawn(Entity entity) {
742 		assert(entity.world == this);
743 	}+/
744 
745 	/**
746 	 * Despawns an entity.
747 	 */
748 	public final void despawn(T:Entity)(T entity) if(!isPlayer!T) {
749 		if(entity.id in this.w_entities) {
750 			entity.setAsDespawned();
751 			this.w_entities.remove(entity.id);
752 			//TODO call the event
753 			foreach(ref Entity e ; entity.viewers) {
754 				e.hide(entity);
755 			}
756 			foreach(ref Entity e ; entity.watchlist) {
757 				entity.hide(e);
758 			}
759 			atomicOp!"-="(this.info.entities, 1);
760 		}
761 	}
762 
763 	/**
764 	 * Drops an item.
765 	 */
766 	public final ItemEntity drop(Slot slot, EntityPosition position, EntityPosition motion) {
767 		if(!slot.empty) {
768 			return this.spawn!ItemEntity(position, motion, slot);
769 		} else {
770 			return null;
771 		}
772 	}
773 
774 	/// ditto
775 	public final ItemEntity drop(Slot slot, EntityPosition position) {
776 		float f0 = uniform01!float(this.random) * .1f;
777 		float f1 = uniform01!float(this.random) * PI * 2f;
778 		return this.drop(slot, position, EntityPosition(-sin(f1) * f0, .2f, cos(f1) * f0));
779 	}
780 
781 	/// ditto
782 	public final void drop(Block from, BlockPosition position) {
783 		foreach(slot ; from.drops(this, null, null)) {
784 			this.drop(slot, position.entityPosition + .5);
785 		}
786 	}
787 
788 	/**
789 	 * Stikes a lightning.
790 	 */
791 	public final void strike(bool visual=false)(EntityPosition position) {
792 		this.w_players.call!"sendLightning"(new Lightning(this, position));
793 		static if(!visual) {
794 			//TODO do the damages
795 			//TODO create the fire
796 		}
797 	}
798 
799 	/// ditto
800 	public final void strike(Entity entity) {
801 		this.strike(entity.position);
802 	}
803 
804 	public void explode(bool breakBlocks=true)(EntityPosition position, float power, Living damager=null) {
805 
806 		enum float rays = 16;
807 		enum float half_rays = (rays - 1) / 2;
808 
809 		enum float length = .3;
810 		enum float attenuation = length * .75;
811 		
812 		Tuple!(BlockPosition, Block*)[] explodedBlocks;
813 
814 		void explodeImpl(EntityPosition ray) {
815 
816 			auto pointer = position.dup;
817 		
818 			for(double blastForce=uniform!"[]"(.4f, 1.3f, this.random)*power; blastForce>0; blastForce-=attenuation) {
819 				auto pos = cast(BlockPosition)pointer + [pointer.x < 0 ? 0 : 1, pointer.y < 0 ? 0 : 1, pointer.z < 0 ? 0 : 1];
820 				//if(pos.y < 0 || pos.y >= 256) break; //TODO use a constant
821 				auto block = pos in this;
822 				if(block) {
823 					blastForce -= ((*block).blastResistance / 5 + length) * length;
824 					if(blastForce <= 0) break;
825 					static if(breakBlocks) {
826 						import std.typecons : tuple;
827 						explodedBlocks ~= tuple(pos, block);
828 					}
829 				}
830 				pointer += ray;
831 			}
832 
833 		}
834 
835 		template Range(float max, E...) {
836 			static if(E.length >= max) {
837 				alias Range = E;
838 			} else {
839 				alias Range = Range!(max, E, E[$-1] + 1);
840 			}
841 		}
842 
843 		foreach(x ; Range!(rays, 0)) {
844 			foreach(y ; Range!(rays, 0)) {
845 				foreach(z ; Range!(rays, 0)) {
846 					static if(x == 0 || x == rays - 1 || y == 0 || y == rays - 1 || z == 0 || z == rays - 1) {
847 
848 						enum ray = (){
849 							auto ret = EntityPosition(x, y, z) / half_rays - 1;
850 							ret.length = length;
851 							return ret;
852 						}();
853 						explodeImpl(ray);
854 
855 					}
856 				}
857 			}
858 		}
859 
860 		// send packets
861 		this.players.call!"sendExplosion"(position, power, new Vector3!byte[0]);
862 
863 		foreach(exploded ; explodedBlocks) {
864 			auto block = this[exploded[0]];
865 			if(block != Blocks.air) {
866 				this.opIndexAssign!true(Blocks.air, exploded[0]); //TODO use packet's fields
867 				if(this.random.range(0f, power) <= 1) {
868 					this.drop(block, exploded[0]);
869 					//TODO drop experience
870 				}
871 			}
872 		}
873 
874 	}
875 
876 	public void explode(bool breakBlocks=true)(BlockPosition position, float power, Living damager=null) {
877 		return this.explode!(breakBlocks)(position.entityPosition, power, damager);
878 	}
879 
880 	/**
881 	 * Gets an entity from an id.
882 	 */
883 	public final @safe Entity entity(uint eid) {
884 		auto ret = eid in this.w_entities;
885 		return ret ? *ret : this.player(eid);
886 	}
887 
888 	/**
889 	 * Gets a player from an id.
890 	 */
891 	public final @safe Player player(uint eid) {
892 		auto ret = eid in this.w_players;
893 		return ret ? *ret : null;
894 	}
895 
896 	// CHUNKS
897 
898 	/**
899 	 * Gets an associative array with every chunk loaded in
900 	 * the world.
901 	 */
902 	public final pure nothrow @property @safe @nogc Chunk[int][int] chunks() {
903 		return this.n_chunks;
904 	}
905 
906 	/**
907 	 * Gets the number of loaded chunks in the world and
908 	 * its children.
909 	 */
910 	public final pure @property @safe @nogc size_t loadedChunks(bool children=false)() {
911 		size_t ret = 0;
912 		foreach(chunks ; this.n_chunks) {
913 			ret += chunks.length;
914 		}
915 		static if(children) {
916 			foreach(child ; this.n_children) {
917 				ret += child.loadedChunks!true;
918 			}
919 		}
920 		return ret;
921 	}
922 
923 	/**
924 	 * Gets a chunk.
925 	 * Returns: the chunk at given position or null if the chunk doesn't exist
926 	 */
927 	public final @safe Chunk opIndex(int x, int z) {
928 		return this.opIndex(ChunkPosition(x, z));
929 	}
930 
931 	/// ditto
932 	public final @safe Chunk opIndex(ChunkPosition position) {
933 		auto ret = position in this;
934 		return ret ? *ret : null;
935 	}
936 
937 	/**
938 	 * Gets a pointer to the chunk at the given position.
939 	 * Example:
940 	 * ---
941 	 * if(ChunkPosition(100, 100) in world) {
942 	 *    log("world doesn't have the chunk at 100, 100");
943 	 * }
944 	 * ---
945 	 */
946 	public final @safe Chunk* opBinaryRight(string op : "in")(ChunkPosition position) {
947 		auto a = position.x in this.n_chunks;
948 		return a ? position.z in *a : null;
949 	}
950 	
951 	/**
952 	 * Sets a chunk.
953 	 * Example:
954 	 * ---
955 	 * auto chunk = new Chunk(world, ChunkPosition(1, 2));
956 	 * world[] = chunk;
957 	 * assert(world[1, 2] == chunk);
958 	 * ---
959 	 */
960 	public final Chunk opIndexAssign(Chunk chunk) {
961 		atomicOp!"+="(this.info.chunks, 1);
962 		chunk.saveChangedBlocks = true;
963 		return this.n_chunks[chunk.x][chunk.z] = chunk;
964 	}
965 
966 	/**
967 	 * Generates and sets a chunk.
968 	 * Returns: the generated chunk
969 	 */
970 	public Chunk generate(ChunkPosition position) {
971 		return this[] = this.generator.generate(position);
972 	}
973 
974 	/// ditto
975 	public Chunk generate(int x, int z) {
976 		return this.generate(ChunkPosition(x, z));
977 	}
978 
979 	/**
980 	 * Unloads and removes a chunk.
981 	 * Returns: true if the chunk was removed, false otherwise
982 	 */
983 	public bool unload(ChunkPosition position) {
984 		if(this.defaultChunks.canFind(position)) return false;
985 		auto chunk = position in this;
986 		if(chunk) {
987 			this.n_chunks[position.x].remove(position.z);
988 			if(this.n_chunks[position.x].length == 0) this.n_chunks.remove(position.x);
989 			(*chunk).unload();
990 			return true;
991 		}
992 		return false;
993 	}
994 
995 	// Handles the player's chunk request
996 	public final void playerUpdateRadius(Player player) {
997 		if(player.viewDistance > this.viewDistance) player.viewDistance = this.viewDistance;
998 		//TODO allow disallowing auto-sending
999 		//if(this.rules.chunksAutosending) {
1000 			ChunkPosition pos = player.chunk;
1001 			if(player.last_chunk_position != pos) {
1002 				player.last_chunk_position = pos;
1003 
1004 				ChunkPosition[] new_chunks;
1005 				foreach(int x ; pos.x-player.viewDistance.to!int..pos.x+player.viewDistance.to!int) {
1006 					foreach(int z ; pos.z-player.viewDistance.to!int..pos.z+player.viewDistance.to!int) {
1007 						ChunkPosition c = ChunkPosition(x, z);
1008 						if(distance(c, pos) <= player.viewDistance) {
1009 							new_chunks ~= c;
1010 						}
1011 					}
1012 				}
1013 
1014 				// sort 'em
1015 				sort!"a.x + a.z < b.x + b.z"(new_chunks);
1016 
1017 				// send chunks
1018 				foreach(ChunkPosition cp ; new_chunks) {
1019 					if(!player.loaded_chunks.canFind(cp)) {
1020 						auto chunk = cp in this;
1021 						if(chunk) {
1022 							player.sendChunk(*chunk);
1023 						} else {
1024 							player.sendChunk(this.generate(cp));
1025 						}
1026 					}
1027 				}
1028 
1029 				// unload chunks
1030 				foreach(ref ChunkPosition c ; player.loaded_chunks) {
1031 					if(!new_chunks.canFind(c)) {
1032 						//TODO check if it should be deleted from the world's memory
1033 						//this.unload(this[c]);
1034 						player.unloadChunk(c);
1035 					}
1036 				}
1037 
1038 				// set the new chunks
1039 				player.loaded_chunks = new_chunks;
1040 
1041 			}
1042 		//}
1043 
1044 	}
1045 
1046 	// BLOCKS
1047 
1048 	/**
1049 	 * Gets a block.
1050 	 * Returns: an instance of block, which is never null
1051 	 * Example:
1052 	 * ---
1053 	 * if(world[0, 0, 0] != Blocks.BEDROCK) {
1054 	 *    log("0,0,0 is not bedrock!");
1055 	 * }
1056 	 * ---
1057 	 */
1058 	public Block opIndex(BlockPosition position) {
1059 		auto block = position in this;
1060 		return block ? *block : *this.blocks[0]; // default block (air)
1061 	}
1062 
1063 	/// ditto
1064 	public Block opIndex(int x, uint y, int z) {
1065 		return this.opIndex(BlockPosition(x, y, z));
1066 	}
1067 
1068 	//TODO documentation
1069 	public Block* opBinaryRight(string op : "in")(BlockPosition position) {
1070 		auto chunk = ChunkPosition(position.x >> 4, position.z >> 4) in this;
1071 		return chunk ? (*chunk)[position.x & 15, position.y, position.z & 15] : null;
1072 	}
1073 
1074 	/**
1075 	 * Gets a tile.
1076 	 */
1077 	public @safe T tileAt(T=Tile)(int x, uint y, int z) if(is(T == class) || is(T == interface)) {
1078 		auto chunk = ChunkPosition(x >> 4, z >> 4) in this;
1079 		return chunk ? (*chunk).tileAt!T(BlockPosition(x & 15, y, z & 15)) : null;
1080 	}
1081 
1082 	/// ditto
1083 	public @safe T tileAt(T=Tile)(BlockPosition position) {
1084 		return this.tileAt!T(position.x, position.y, position.z);
1085 	}
1086 
1087 	/**
1088 	 * Sets a block.
1089 	 * Example:
1090 	 * ---
1091 	 * world[0, 55, 12] = Blocks.grass;
1092 	 * world[12, 55, 789] = Blocks.chest; // not a tile!
1093 	 * ---
1094 	 */
1095 	public Block* opIndexAssign(bool sendUpdates=true, T)(T block, BlockPosition position) if(is(T == block_t) || is(T == block_t[]) || is(T == Block*)) {
1096 		auto chunk = ChunkPosition(position.x >> 4, position.z >> 4) in this;
1097 		if(chunk) {
1098 
1099 			Block* ptr = (*chunk)[position.x & 15, position.y, position.z & 15];
1100 			if(ptr) {
1101 				(*ptr).onRemoved(this, position, Remove.unset);
1102 			}
1103 
1104 			static if(is(T == block_t[])) {
1105 				block_t b = block[0];
1106 			} else {
1107 				alias b = block;
1108 			}
1109 
1110 			Block* nb = ((*chunk)[position.x & 15, position.y, position.z & 15] = b);
1111 
1112 			// set as to update
1113 			//TODO move this in the chunk
1114 			static if(sendUpdates) this.updated_blocks ~= PlacedBlock(position, nb ? (*nb).data : sul.blocks.Blocks.air);
1115 
1116 			// call the update function
1117 			if(this.updateBlocks) {
1118 				if(nb) (*nb).onUpdated(this, position, Update.placed);
1119 				this.updateBlock(position + [0, 1, 0]);
1120 				this.updateBlock(position + [1, 0, 0]);
1121 				this.updateBlock(position + [0, 0, 1]);
1122 				this.updateBlock(position - [0, 1, 0]);
1123 				this.updateBlock(position - [1, 0, 0]);
1124 				this.updateBlock(position - [0, 0, 1]);
1125 			}
1126 
1127 			return nb;
1128 
1129 		}
1130 		return null;
1131 	}
1132 
1133 	/// ditto
1134 	public Block* opIndexAssign(T)(T block, int x, uint y, int z) if(is(T == block_t) || is(T == block_t[]) || is(T == Block*)) {
1135 		return this.opIndexAssign(block, BlockPosition(x, y, z));
1136 	}
1137 
1138 	/**
1139 	 * Sets a tile.
1140 	 */
1141 	public void opIndexAssign(T)(T tile, BlockPosition position) if(is(T : Tile) && is(T : Block)) {
1142 		assert(!tile.placed, "This tile has already been placed: " ~ to!string(tile) ~ " at " ~ to!string(position));
1143 		// place the block
1144 		this[position] = tile.id;
1145 		auto chunk = ChunkPosition(position.x >> 4, position.z >> 4) in this;
1146 		if(chunk) {
1147 			// then set it as placed here
1148 			tile.place(this, position);
1149 			// and register the tile in the chunk
1150 			(*chunk).registerTile(tile);
1151 		}
1152 	}
1153 
1154 	/// ditto
1155 	public void opIndexAssign(T)(T tile, int x, uint y, int z) if(is(T : Tile) && is(T : Block)) {
1156 		this.opIndexAssign(tile, BlockPosition(x, y, z));
1157 	}
1158 
1159 	/**
1160 	 * Sets the same block in a rectangualar area.
1161 	 * This method is optimised for building as it uses a cached pointer
1162 	 * instead of getting it every time and it doesn't call any block
1163 	 * update.
1164 	 * Example:
1165 	 * ---
1166 	 * // sets a chunk to stone
1167 	 * world[0..16, 0..$, 0..16] = Blocks.stone;
1168 	 * 
1169 	 * // sets an area to air
1170 	 * world[0..16, 64..128, 0..16] = Blocks.air;
1171 	 * 
1172 	 * // sets a 1-block-high layer only
1173 	 * world[0..16, 64, 0..16] = Blocks.beetroot0;
1174 	 * ---
1175 	 */
1176 	public final void opIndexAssign(block_t b, Slice x, Slice y, Slice z) {
1177 		auto block = b in this.blocks;
1178 		this.updateBlocks = false;
1179 		foreach(px ; x.min..x.max) {
1180 			foreach(py ; y.min..y.max) {
1181 				foreach(pz ; z.min..z.max) {
1182 					this[px, py, pz] = block;
1183 				}
1184 			}
1185 		}
1186 		this.updateBlocks = true;
1187 	}
1188 
1189 	/// ditto
1190 	public final void opIndexAssign(block_t b, int x, Slice y, Slice z) {
1191 		this[x..x+1, y, z] = b;
1192 	}
1193 
1194 	/// ditto
1195 	public final void opIndexAssign(block_t b, Slice x, uint y, Slice z) {
1196 		this[x, y..y+1, z] = b;
1197 	}
1198 
1199 	/// ditto
1200 	public final void opIndexAssign(block_t b, Slice x, Slice y, int z) {
1201 		this[x, y, z..z+1] = b;
1202 	}
1203 
1204 	/// ditto
1205 	public final void opIndexAssign(block_t b, int x, uint y, Slice z) {
1206 		this[x..x+1, y..y+1, z] = b;
1207 	}
1208 
1209 	/// ditto
1210 	public final void opIndexAssign(block_t b, int x, Slice y, int z) {
1211 		this[x..x+1, y, z..z+1] = b;
1212 	}
1213 
1214 	/// ditto
1215 	public final void opIndexAssign(block_t b, Slice x, uint y, int z) {
1216 		this[x, y..y+1, z..z+1] = b;
1217 	}
1218 
1219 	public size_t replace(block_t from, block_t to) {
1220 		return this.replace(from in this.blocks, to in this.blocks);
1221 	}
1222 
1223 	public size_t replace(Block* from, Block* to) {
1224 		size_t ret = 0;
1225 		foreach(cc ; this.n_chunks) {
1226 			foreach(c ; cc) {
1227 				ret += this.replaceImpl(c, from, to);
1228 			}
1229 		}
1230 		return ret;
1231 	}
1232 
1233 	public size_t replace(block_t from, block_t to, BlockPosition fromp, BlockPosition top) {
1234 		if(fromp.x < top.x && fromp.y < top.y && fromp.z < top.z) {
1235 			//TODO
1236 			return 0;
1237 		} else {
1238 			return 0;
1239 		}
1240 	}
1241 
1242 	protected size_t replaceImpl(ref Chunk chunk, Block* from, Block* to) {
1243 		//TODO
1244 		return 0;
1245 	}
1246 
1247 	/// function called by a tile when its data is updated
1248 	public final void updateTile(Tile tile, BlockPosition position) {
1249 		this.updated_tiles[tile.tid] = tile;
1250 	}
1251 
1252 	protected final void updateBlock(BlockPosition position) {
1253 		auto block = position in this;
1254 		if(block) (*block).onUpdated(this, position, Update.nearestChanged);
1255 	}
1256 
1257 	public @safe Slice opSlice(size_t pos)(int min, int max) {
1258 		return Slice(min, max);
1259 	}
1260 
1261 	/// schedules a block update
1262 	public @safe void scheduleBlockUpdate(Block block, uint time) {
1263 		/*if(this.rules.scheduledTicks) {
1264 			this.scheduled_updates ~= ScheduledUpdate(block, time);
1265 		}*/
1266 	}
1267 
1268 	/**
1269 	 * Registers a command.
1270 	 */
1271 	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) {
1272 		command = command.toLower;
1273 		if(command !in this.commands) this.commands[command] = new Command(command, description, aliases, permissionLevel, permissions, hidden);
1274 		auto ptr = command in this.commands;
1275 		(*ptr).add!func(del, implemented);
1276 	}
1277 
1278 	/**
1279 	 * Unregisters a command.
1280 	 */
1281 	public void unregisterCommand(string command) {
1282 		this.commands.remove(command);
1283 	}
1284 
1285 	/**
1286 	 * Registers a task.
1287 	 * Params:
1288 	 *		task = a delegate of a function that will be called every interval
1289 	 *		interval = number of ticks indicating between the calls
1290 	 *		repeat = number of times to repeat the task
1291 	 * Returns:
1292 	 * 		the new task id that can be used to remove the task
1293 	 */
1294 	public @safe size_t addTask(void delegate() task, size_t interval, size_t repeat=size_t.max) {
1295 		return this.tasks.add(task, interval, repeat, this.ticks);
1296 	}
1297 	
1298 	/// ditto
1299 	alias addTask schedule;
1300 	
1301 	/**
1302 	 * Executes a task one time after the given ticks.
1303 	 */
1304 	public @safe size_t delay(void delegate() task, size_t timeout) {
1305 		return this.addTask(task, timeout, 1);
1306 	}
1307 	
1308 	/**
1309 	 * Removes a task using the task's delegate or the id returned
1310 	 * by the addTask function.
1311 	 */
1312 	public @safe void removeTask(void delegate() task) {
1313 		this.tasks.remove(task);
1314 	}
1315 	
1316 	/// ditto
1317 	public @safe void removeTask(size_t tid) {
1318 		this.tasks.remove(tid);
1319 	}
1320 
1321 	public override @safe bool opEquals(Object o) {
1322 		if(cast(World)o) return this.id == (cast(World)o).id;
1323 		else return false;
1324 	}
1325 
1326 	/** Grows a tree in the given world. */
1327 	public static void growTree(World world, BlockPosition position, ushort[] trunk=Blocks.oakWood, ushort leaves=Blocks.oakLeavesDecay) {
1328 		uint height = uniform(3u, 6u, world.random);
1329 		foreach(uint i ; 0..height) {
1330 			world[position + [0, i, 0]] = trunk[0];
1331 		}
1332 
1333 		foreach(uint i ; 0..2) {
1334 			foreach(int x ; -2..3) {
1335 				foreach(int z ; -2..3) {
1336 					world[position + [x, height + i, z]] = leaves;
1337 				}
1338 			}
1339 		}
1340 		foreach(int x ; -1..2) {
1341 			foreach(int z ; -1..2) {
1342 				world[position + [x, height + 2, z]] = leaves;
1343 			}
1344 		}
1345 		world[position + [0, height + 3, 0]] = leaves;
1346 		world[position + [-1, height + 3, 0]] = leaves;
1347 		world[position + [1, height + 3, 0]] = leaves;
1348 		world[position + [0, height + 3, -1]] = leaves;
1349 		world[position + [0, height + 3, 1]] = leaves;
1350 	}
1351 
1352 }
1353 
1354 private alias Slice = Tuple!(int, "min", int, "max");
1355 
1356 private struct ScheduledUpdate {
1357 
1358 	public Block block;
1359 	public uint time;
1360 
1361 	public @safe @nogc this(ref Block block, uint time) {
1362 		this.block = block;
1363 		this.time = time;
1364 	}
1365 
1366 }
1367 
1368 enum Time : uint {
1369 
1370 	day = 1000,
1371 	night = 13000,
1372 	midnight = 18000,
1373 	noon = 6000,
1374 	sunrise = 23000,
1375 	sunset = 12000,
1376 
1377 }