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 }