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 }