diff --git a/generated/lang/KnownTranslationFactory.php b/generated/lang/KnownTranslationFactory.php index 18178855002..ec19837201f 100644 --- a/generated/lang/KnownTranslationFactory.php +++ b/generated/lang/KnownTranslationFactory.php @@ -535,6 +535,40 @@ public static function commands_unbanip_usage() : Translatable{ return new Translatable(KnownTranslationKeys::COMMANDS_UNBANIP_USAGE, []); } + public static function commands_weather_clear() : Translatable{ + return new Translatable(KnownTranslationKeys::COMMANDS_WEATHER_CLEAR, []); + } + + public static function commands_weather_disabled() : Translatable{ + return new Translatable(KnownTranslationKeys::COMMANDS_WEATHER_DISABLED, []); + } + + public static function commands_weather_query(Translatable|string $param0) : Translatable{ + return new Translatable(KnownTranslationKeys::COMMANDS_WEATHER_QUERY, [ + 0 => $param0, + ]); + } + + public static function commands_weather_query_clear() : Translatable{ + return new Translatable(KnownTranslationKeys::COMMANDS_WEATHER_QUERY_CLEAR, []); + } + + public static function commands_weather_query_rain() : Translatable{ + return new Translatable(KnownTranslationKeys::COMMANDS_WEATHER_QUERY_RAIN, []); + } + + public static function commands_weather_query_thunder() : Translatable{ + return new Translatable(KnownTranslationKeys::COMMANDS_WEATHER_QUERY_THUNDER, []); + } + + public static function commands_weather_rain() : Translatable{ + return new Translatable(KnownTranslationKeys::COMMANDS_WEATHER_RAIN, []); + } + + public static function commands_weather_thunder() : Translatable{ + return new Translatable(KnownTranslationKeys::COMMANDS_WEATHER_THUNDER, []); + } + public static function commands_whitelist_add_success(Translatable|string $param0) : Translatable{ return new Translatable(KnownTranslationKeys::COMMANDS_WHITELIST_ADD_SUCCESS, [ 0 => $param0, @@ -2027,6 +2061,18 @@ public static function pocketmine_command_version_usage() : Translatable{ return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_VERSION_USAGE, []); } + public static function pocketmine_command_weather_description() : Translatable{ + return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_WEATHER_DESCRIPTION, []); + } + + public static function pocketmine_command_weather_disable() : Translatable{ + return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_WEATHER_DISABLE, []); + } + + public static function pocketmine_command_weather_usage() : Translatable{ + return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_WEATHER_USAGE, []); + } + public static function pocketmine_command_whitelist_description() : Translatable{ return new Translatable(KnownTranslationKeys::POCKETMINE_COMMAND_WHITELIST_DESCRIPTION, []); } @@ -2513,6 +2559,10 @@ public static function pocketmine_permission_command_version() : Translatable{ return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_VERSION, []); } + public static function pocketmine_permission_command_weather() : Translatable{ + return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_WEATHER, []); + } + public static function pocketmine_permission_command_whitelist_add() : Translatable{ return new Translatable(KnownTranslationKeys::POCKETMINE_PERMISSION_COMMAND_WHITELIST_ADD, []); } diff --git a/generated/lang/KnownTranslationKeys.php b/generated/lang/KnownTranslationKeys.php index 67c4e7a70d6..ff8953414cc 100644 --- a/generated/lang/KnownTranslationKeys.php +++ b/generated/lang/KnownTranslationKeys.php @@ -122,6 +122,14 @@ final class KnownTranslationKeys{ public const COMMANDS_UNBANIP_INVALID = "commands.unbanip.invalid"; public const COMMANDS_UNBANIP_SUCCESS = "commands.unbanip.success"; public const COMMANDS_UNBANIP_USAGE = "commands.unbanip.usage"; + public const COMMANDS_WEATHER_CLEAR = "commands.weather.clear"; + public const COMMANDS_WEATHER_DISABLED = "commands.weather.disabled"; + public const COMMANDS_WEATHER_QUERY = "commands.weather.query"; + public const COMMANDS_WEATHER_QUERY_CLEAR = "commands.weather.query.clear"; + public const COMMANDS_WEATHER_QUERY_RAIN = "commands.weather.query.rain"; + public const COMMANDS_WEATHER_QUERY_THUNDER = "commands.weather.query.thunder"; + public const COMMANDS_WEATHER_RAIN = "commands.weather.rain"; + public const COMMANDS_WEATHER_THUNDER = "commands.weather.thunder"; public const COMMANDS_WHITELIST_ADD_SUCCESS = "commands.whitelist.add.success"; public const COMMANDS_WHITELIST_ADD_USAGE = "commands.whitelist.add.usage"; public const COMMANDS_WHITELIST_DISABLED = "commands.whitelist.disabled"; @@ -443,6 +451,9 @@ final class KnownTranslationKeys{ public const POCKETMINE_COMMAND_VERSION_SERVERSOFTWARENAME = "pocketmine.command.version.serverSoftwareName"; public const POCKETMINE_COMMAND_VERSION_SERVERSOFTWAREVERSION = "pocketmine.command.version.serverSoftwareVersion"; public const POCKETMINE_COMMAND_VERSION_USAGE = "pocketmine.command.version.usage"; + public const POCKETMINE_COMMAND_WEATHER_DESCRIPTION = "pocketmine.command.weather.description"; + public const POCKETMINE_COMMAND_WEATHER_DISABLE = "pocketmine.command.weather.disable"; + public const POCKETMINE_COMMAND_WEATHER_USAGE = "pocketmine.command.weather.usage"; public const POCKETMINE_COMMAND_WHITELIST_DESCRIPTION = "pocketmine.command.whitelist.description"; public const POCKETMINE_COMMAND_XP_DESCRIPTION = "pocketmine.command.xp.description"; public const POCKETMINE_COMMAND_XP_USAGE = "pocketmine.command.xp.usage"; @@ -549,6 +560,7 @@ final class KnownTranslationKeys{ public const POCKETMINE_PERMISSION_COMMAND_UNBAN_IP = "pocketmine.permission.command.unban.ip"; public const POCKETMINE_PERMISSION_COMMAND_UNBAN_PLAYER = "pocketmine.permission.command.unban.player"; public const POCKETMINE_PERMISSION_COMMAND_VERSION = "pocketmine.permission.command.version"; + public const POCKETMINE_PERMISSION_COMMAND_WEATHER = "pocketmine.permission.command.weather"; public const POCKETMINE_PERMISSION_COMMAND_WHITELIST_ADD = "pocketmine.permission.command.whitelist.add"; public const POCKETMINE_PERMISSION_COMMAND_WHITELIST_DISABLE = "pocketmine.permission.command.whitelist.disable"; public const POCKETMINE_PERMISSION_COMMAND_WHITELIST_ENABLE = "pocketmine.permission.command.whitelist.enable"; diff --git a/generated/lang/KnownTranslationParameterInfo.php b/generated/lang/KnownTranslationParameterInfo.php index 469e4b76e7b..36c0cde1269 100644 --- a/generated/lang/KnownTranslationParameterInfo.php +++ b/generated/lang/KnownTranslationParameterInfo.php @@ -125,6 +125,14 @@ final class KnownTranslationParameterInfo{ Keys::COMMANDS_UNBANIP_INVALID => [], Keys::COMMANDS_UNBANIP_SUCCESS => ["0"], Keys::COMMANDS_UNBANIP_USAGE => [], + Keys::COMMANDS_WEATHER_CLEAR => [], + Keys::COMMANDS_WEATHER_DISABLED => [], + Keys::COMMANDS_WEATHER_QUERY => ["0"], + Keys::COMMANDS_WEATHER_QUERY_CLEAR => [], + Keys::COMMANDS_WEATHER_QUERY_RAIN => [], + Keys::COMMANDS_WEATHER_QUERY_THUNDER => [], + Keys::COMMANDS_WEATHER_RAIN => [], + Keys::COMMANDS_WEATHER_THUNDER => [], Keys::COMMANDS_WHITELIST_ADD_SUCCESS => ["0"], Keys::COMMANDS_WHITELIST_ADD_USAGE => [], Keys::COMMANDS_WHITELIST_DISABLED => [], @@ -446,6 +454,9 @@ final class KnownTranslationParameterInfo{ Keys::POCKETMINE_COMMAND_VERSION_SERVERSOFTWARENAME => ["serverSoftwareName"], Keys::POCKETMINE_COMMAND_VERSION_SERVERSOFTWAREVERSION => ["serverSoftwareVersion", "serverGitHash"], Keys::POCKETMINE_COMMAND_VERSION_USAGE => [], + Keys::POCKETMINE_COMMAND_WEATHER_DESCRIPTION => [], + Keys::POCKETMINE_COMMAND_WEATHER_DISABLE => [], + Keys::POCKETMINE_COMMAND_WEATHER_USAGE => [], Keys::POCKETMINE_COMMAND_WHITELIST_DESCRIPTION => [], Keys::POCKETMINE_COMMAND_XP_DESCRIPTION => [], Keys::POCKETMINE_COMMAND_XP_USAGE => [], @@ -552,6 +563,7 @@ final class KnownTranslationParameterInfo{ Keys::POCKETMINE_PERMISSION_COMMAND_UNBAN_IP => [], Keys::POCKETMINE_PERMISSION_COMMAND_UNBAN_PLAYER => [], Keys::POCKETMINE_PERMISSION_COMMAND_VERSION => [], + Keys::POCKETMINE_PERMISSION_COMMAND_WEATHER => [], Keys::POCKETMINE_PERMISSION_COMMAND_WHITELIST_ADD => [], Keys::POCKETMINE_PERMISSION_COMMAND_WHITELIST_DISABLE => [], Keys::POCKETMINE_PERMISSION_COMMAND_WHITELIST_ENABLE => [], diff --git a/resources/translations/eng.ini b/resources/translations/eng.ini index e2205048a5d..635ab3900c3 100644 --- a/resources/translations/eng.ini +++ b/resources/translations/eng.ini @@ -98,6 +98,14 @@ commands.unban.usage=/pardon commands.unbanip.invalid=You have entered an invalid IP address commands.unbanip.success=Unbanned IP address {%0} commands.unbanip.usage=/pardon-ip
+commands.weather.clear=Changing to clear weather +commands.weather.disabled=Weather Cycle isn't enabled in this world. +commands.weather.query=Weather state is: {%0} +commands.weather.query.clear=clear +commands.weather.query.rain=rain +commands.weather.query.thunder=thunder +commands.weather.rain=Changing to rainy weather +commands.weather.thunder=Changing to rain and thunder commands.whitelist.add.success=Added {%0} to the whitelist commands.whitelist.add.usage=/whitelist add commands.whitelist.disabled=Turned off the whitelist @@ -437,6 +445,9 @@ pocketmine.command.version.plugin.website=Website: {%website} pocketmine.command.version.serverSoftwareName=This server is running {%serverSoftwareName} pocketmine.command.version.serverSoftwareVersion=Server version: {%serverSoftwareVersion} (git hash: {%serverGitHash}) pocketmine.command.version.usage=/version [plugin name] +pocketmine.command.weather.description=Sets the weather +pocketmine.command.weather.disable=Weather is disable in this world +pocketmine.command.weather.usage=/weather [duration] pocketmine.command.whitelist.description=Manages the list of players allowed to use this server pocketmine.command.xp.description=Adds or removes player experience pocketmine.command.xp.usage=/xp [player] @@ -543,6 +554,7 @@ pocketmine.permission.command.transferserver=Allows the user to transfer self to pocketmine.permission.command.unban.ip=Allows the user to unban IP addresses pocketmine.permission.command.unban.player=Allows the user to unban players pocketmine.permission.command.version=Allows the user to view the version of the server +pocketmine.permission.command.weather=Allows the player to change the weather pocketmine.permission.command.whitelist.add=Allows the user to add a player to the server whitelist pocketmine.permission.command.whitelist.disable=Allows the user to disable the server whitelist pocketmine.permission.command.whitelist.enable=Allows the user to enable the server whitelist diff --git a/src/Server.php b/src/Server.php index 130bf353cb4..2cd6abe364f 100644 --- a/src/Server.php +++ b/src/Server.php @@ -841,7 +841,8 @@ public function __construct( ServerProperties::AUTO_SAVE => true, ServerProperties::VIEW_DISTANCE => self::DEFAULT_MAX_VIEW_DISTANCE, ServerProperties::XBOX_AUTH => true, - ServerProperties::LANGUAGE => "eng" + ServerProperties::LANGUAGE => "eng", + ServerProperties::WEATHER_ENABLED => true ]) ); diff --git a/src/ServerProperties.php b/src/ServerProperties.php index ddbbd81340a..0331b5b81e6 100644 --- a/src/ServerProperties.php +++ b/src/ServerProperties.php @@ -53,6 +53,7 @@ private function __construct(){ public const SERVER_PORT_IPV4 = "server-port"; public const SERVER_PORT_IPV6 = "server-portv6"; public const VIEW_DISTANCE = "view-distance"; + public const WEATHER_ENABLED = "weather-enabled"; public const WHITELIST = "white-list"; public const XBOX_AUTH = "xbox-auth"; } diff --git a/src/block/Farmland.php b/src/block/Farmland.php index 83bc3456112..eb32d6d2ddf 100644 --- a/src/block/Farmland.php +++ b/src/block/Farmland.php @@ -158,6 +158,11 @@ public function onEntityLand(Entity $entity) : ?float{ protected function canHydrate() : bool{ $world = $this->position->getWorld(); + if($world->isRaining($this->position)){ + $this->waterPositionIndex = self::WATER_POSITION_INDEX_UNKNOWN; + return true; + } + $startX = $this->position->getFloorX() - (int) (self::WATER_SEARCH_HORIZONTAL_LENGTH / 2); $startY = $this->position->getFloorY(); $startZ = $this->position->getFloorZ() - (int) (self::WATER_SEARCH_HORIZONTAL_LENGTH / 2); diff --git a/src/block/Fire.php b/src/block/Fire.php index 62809fb9d80..b1c587e8370 100644 --- a/src/block/Fire.php +++ b/src/block/Fire.php @@ -76,7 +76,11 @@ public function onRandomTick() : void{ $canSpread = true; if(!$down->burnsForever()){ - //TODO: check rain + $world = $this->position->getWorld(); + if($world->isRaining($this->position)){ + return; + } + if($this->age === self::MAX_AGE){ if(!$down->isFlammable() && mt_rand(0, 3) === 3){ //1/4 chance to extinguish $canSpread = false; @@ -187,8 +191,9 @@ private function spreadFire() : void{ if($block->getTypeId() !== BlockTypeIds::AIR){ continue; } - - //TODO: fire can't spread if it's raining in any horizontally adjacent block, or the current one + if($world->isRaining($block->position)){ + continue; + } $encouragement = 0; foreach($block->position->sides() as $vector3){ diff --git a/src/block/LightningRod.php b/src/block/LightningRod.php index 4bee68f916f..8bef2d6519c 100644 --- a/src/block/LightningRod.php +++ b/src/block/LightningRod.php @@ -39,6 +39,8 @@ final class LightningRod extends Transparent implements AnyFacing, CopperMateria use CopperTrait; use AnyFacingTrait; + // TODO: Add lightning attraction behavior when thunderstorms occur + protected function recalculateCollisionBoxes() : array{ $myAxis = Facing::axis($this->facing); diff --git a/src/command/SimpleCommandMap.php b/src/command/SimpleCommandMap.php index 9f54417469f..b76ec1fc7b1 100644 --- a/src/command/SimpleCommandMap.php +++ b/src/command/SimpleCommandMap.php @@ -63,6 +63,7 @@ use pocketmine\command\defaults\TransferServerCommand; use pocketmine\command\defaults\VanillaCommand; use pocketmine\command\defaults\VersionCommand; +use pocketmine\command\defaults\WeatherCommand; use pocketmine\command\defaults\WhitelistCommand; use pocketmine\command\defaults\XpCommand; use pocketmine\command\utils\CommandStringHelper; @@ -134,6 +135,7 @@ private function setDefaultCommands() : void{ new TitleCommand(), new TransferServerCommand(), new VersionCommand(), + new WeatherCommand(), new WhitelistCommand(), new XpCommand(), ]); diff --git a/src/command/defaults/WeatherCommand.php b/src/command/defaults/WeatherCommand.php new file mode 100644 index 00000000000..fba6f2576e5 --- /dev/null +++ b/src/command/defaults/WeatherCommand.php @@ -0,0 +1,118 @@ +setPermission(DefaultPermissionNames::COMMAND_WEATHER); + } + + public function execute(CommandSender $sender, string $label, array $args) : void{ + if($sender instanceof Player){ + $world = $sender->getWorld(); + }else{ + $world = $sender->getServer()->getWorldManager()->getDefaultWorld(); + } + + if($world === null){ + throw new AssumptionFailedError("Failed to retrieve world instance. Default world is not loaded."); + } + + if(!$world->isWeatherEnabled()){ + $sender->sendMessage(KnownTranslationFactory::pocketmine_command_weather_disable()); + return; + } + + if(count($args) < 1){ + $rainLevel = $world->getRainLevel(); + $lightningLevel = $world->getLightningLevel(); + + if($rainLevel > 0.0 && $lightningLevel > 0.0){ + $current = "thunder"; + }elseif($rainLevel > 0.0){ + $current = "rain"; + }else{ + $current = "clear"; + } + + $stateName = match($current){ + "clear" => KnownTranslationFactory::commands_weather_query_clear(), + "rain" => KnownTranslationFactory::commands_weather_query_rain(), + "thunder" => KnownTranslationFactory::commands_weather_query_thunder(), + }; + + $sender->sendMessage(KnownTranslationFactory::commands_weather_query($stateName)); + return; + } + + $type = match(strtolower($args[0])){ + "clear" => "clear", + "rain" => "rain", + "thunder" => "thunder", + default => throw new InvalidCommandSyntaxException(), + }; + + $rainLevel = 0.0; + $lightningLevel = 0.0; + $duration = max(100, (int) ($args[1] ?? 6000)); + + switch($type){ + case "rain": + $rainLevel = 1.0; + $lightningLevel = 0.0; + break; + + case "thunder": + $rainLevel = 1.0; + $lightningLevel = 1.0; + break; + } + $world->setWeather($rainLevel, $lightningLevel, $duration); + + Command::broadcastCommandMessage($sender, + match($type){ + "clear" => KnownTranslationFactory::commands_weather_clear(), + "rain" => KnownTranslationFactory::commands_weather_rain(), + "thunder" => KnownTranslationFactory::commands_weather_thunder(), + } + ); + } +} diff --git a/src/entity/Entity.php b/src/entity/Entity.php index 89f66d6b163..28a91a66855 100644 --- a/src/entity/Entity.php +++ b/src/entity/Entity.php @@ -737,6 +737,11 @@ protected function doOnFireTick(int $tickDiff = 1) : bool{ return false; } + $world = $this->getWorld(); + if($world->isRaining($this->location)){ + $this->extinguish(EntityExtinguishEvent::CAUSE_RAIN); + } + $this->fireTicks -= $tickDiff; if(($this->fireTicks % 20 === 0) || $tickDiff > 20){ diff --git a/src/entity/EntityFactory.php b/src/entity/EntityFactory.php index 091654c4abe..b9d15cf2479 100644 --- a/src/entity/EntityFactory.php +++ b/src/entity/EntityFactory.php @@ -195,6 +195,10 @@ public function __construct(){ 'ThrownTrident' //as above ]); + $this->register(LightningBolt::class, function(World $world, CompoundTag $nbt) : LightningBolt{ + return new LightningBolt(Helper::parseLocation($nbt, $world), $nbt); + }, ['lightning_bolt', 'minecraft:lightning_bolt']); + $this->register(Squid::class, function(World $world, CompoundTag $nbt) : Squid{ return new Squid(Helper::parseLocation($nbt, $world), $nbt); }, ['Squid', 'minecraft:squid']); diff --git a/src/entity/LightningBolt.php b/src/entity/LightningBolt.php new file mode 100644 index 00000000000..f8a93e98c79 --- /dev/null +++ b/src/entity/LightningBolt.php @@ -0,0 +1,77 @@ +setNameTagVisible(false); + } + + public function onFirstUpdate(int $currentTick) : void{ + parent::onFirstUpdate($currentTick); + $this->getWorld()->addSound($this->location, new ThunderSound()); + foreach($this->getWorld()->getNearbyEntities($this->boundingBox->expandedCopy(3, 3, 3), $this) as $entity){ + if($entity instanceof Living){ + $entity->attack(new EntityDamageEvent($entity, EntityDamageEvent::CAUSE_LIGHTNING, 5.0)); + } + } + } + + public function entityBaseTick(int $tickDiff = 1) : bool{ + $hasUpdate = parent::entityBaseTick($tickDiff); + + if($this->ticksLived > 20){ + $this->flagForDespawn(); + } + + return $hasUpdate; + } +} diff --git a/src/event/entity/EntityDamageEvent.php b/src/event/entity/EntityDamageEvent.php index 7c3e2aaa40c..4506d4c056b 100644 --- a/src/event/entity/EntityDamageEvent.php +++ b/src/event/entity/EntityDamageEvent.php @@ -65,6 +65,7 @@ class EntityDamageEvent extends EntityEvent implements Cancellable{ public const CAUSE_CUSTOM = 14; public const CAUSE_STARVATION = 15; public const CAUSE_FALLING_BLOCK = 16; + public const CAUSE_LIGHTNING = 17; private float $baseDamage; private float $originalBase; diff --git a/src/event/world/WeatherChangeEvent.php b/src/event/world/WeatherChangeEvent.php new file mode 100644 index 00000000000..810baf7d24b --- /dev/null +++ b/src/event/world/WeatherChangeEvent.php @@ -0,0 +1,53 @@ +world; } + + public function getOldRainLevel() : float{ return $this->oldRainLevel; } + public function getOldLightningLevel() : float{ return $this->oldLightningLevel; } + + public function getNewRainLevel() : float{ return $this->newRainLevel; } + public function getNewLightningLevel() : float{ return $this->newLightningLevel; } + + public function setNewRainLevel(float $rainLevel) : void{ $this->newRainLevel = $rainLevel; } + public function setNewLightningLevel(float $thunderLevel) : void{ $this->newLightningLevel = $thunderLevel; } +} diff --git a/src/network/mcpe/NetworkSession.php b/src/network/mcpe/NetworkSession.php index 8787a1a0519..e508fa79ec6 100644 --- a/src/network/mcpe/NetworkSession.php +++ b/src/network/mcpe/NetworkSession.php @@ -64,6 +64,7 @@ use pocketmine\network\mcpe\protocol\ClientboundCloseFormPacket; use pocketmine\network\mcpe\protocol\ClientboundPacket; use pocketmine\network\mcpe\protocol\DisconnectPacket; +use pocketmine\network\mcpe\protocol\LevelEventPacket; use pocketmine\network\mcpe\protocol\ModalFormRequestPacket; use pocketmine\network\mcpe\protocol\MovePlayerPacket; use pocketmine\network\mcpe\protocol\NetworkChunkPublisherUpdatePacket; @@ -97,6 +98,7 @@ use pocketmine\network\mcpe\protocol\types\command\CommandPermissions; use pocketmine\network\mcpe\protocol\types\CompressionAlgorithm; use pocketmine\network\mcpe\protocol\types\DimensionIds; +use pocketmine\network\mcpe\protocol\types\LevelEvent; use pocketmine\network\mcpe\protocol\types\PlayerListEntry; use pocketmine\network\mcpe\protocol\types\PlayerPermissions; use pocketmine\network\mcpe\protocol\UpdateAbilitiesPacket; @@ -1311,10 +1313,11 @@ public function stopUsingChunk(int $chunkX, int $chunkZ) : void{ public function onEnterWorld() : void{ if($this->player !== null){ $world = $this->player->getWorld(); + $this->syncWorldTime($world->getTime()); $this->syncWorldDifficulty($world->getDifficulty()); $this->syncWorldSpawnPoint($world->getSpawnLocation()); - //TODO: weather needs to be synced here (when implemented) + $this->syncWorldWeather($world); } } @@ -1326,6 +1329,25 @@ public function syncWorldDifficulty(int $worldDifficulty) : void{ $this->sendDataPacket(SetDifficultyPacket::create($worldDifficulty)); } + public function syncWorldWeather(World $world) : void{ + $rainLevel = (int) ($world->getRainLevel() * 65535); + $lightningLevel = (int) ($world->getLightningLevel() * 65535); + + $packets = []; + + $packets[] = $rainLevel > 0 + ? LevelEventPacket::create(LevelEvent::START_RAIN, $rainLevel, null) + : LevelEventPacket::create(LevelEvent::STOP_RAIN, 0, null); + + $packets[] = $lightningLevel > 0 + ? LevelEventPacket::create(LevelEvent::START_THUNDER, $lightningLevel, null) + : LevelEventPacket::create(LevelEvent::STOP_THUNDER, 0, null); + + foreach($packets as $packet){ + $this->sendDataPacket($packet); + } + } + public function getInvManager() : ?InventoryManager{ return $this->invManager; } diff --git a/src/network/mcpe/handler/PreSpawnPacketHandler.php b/src/network/mcpe/handler/PreSpawnPacketHandler.php index bbd05c48dd0..06b37b3905c 100644 --- a/src/network/mcpe/handler/PreSpawnPacketHandler.php +++ b/src/network/mcpe/handler/PreSpawnPacketHandler.php @@ -80,8 +80,8 @@ public function setUp() : void{ $levelSettings->hasAchievementsDisabled = true; $levelSettings->time = $world->getTime(); $levelSettings->eduEditionOffer = 0; - $levelSettings->rainLevel = 0; //TODO: implement these properly - $levelSettings->lightningLevel = 0; + $levelSettings->rainLevel = (int) ($world->getRainLevel() * 65535); + $levelSettings->lightningLevel = (int) ($world->getLightningLevel() * 65535); $levelSettings->commandsEnabled = true; $levelSettings->gameRules = [ "naturalregeneration" => new BoolGameRule(false, false), //Hack for client side regeneration diff --git a/src/permission/DefaultPermissionNames.php b/src/permission/DefaultPermissionNames.php index a916e813c05..73a1b32c9db 100644 --- a/src/permission/DefaultPermissionNames.php +++ b/src/permission/DefaultPermissionNames.php @@ -78,6 +78,7 @@ final class DefaultPermissionNames{ public const COMMAND_UNBAN_IP = "pocketmine.command.unban.ip"; public const COMMAND_UNBAN_PLAYER = "pocketmine.command.unban.player"; public const COMMAND_VERSION = "pocketmine.command.version"; + public const COMMAND_WEATHER = "pocketmine.command.weather"; public const COMMAND_WHITELIST_ADD = "pocketmine.command.whitelist.add"; public const COMMAND_WHITELIST_DISABLE = "pocketmine.command.whitelist.disable"; public const COMMAND_WHITELIST_ENABLE = "pocketmine.command.whitelist.enable"; diff --git a/src/permission/DefaultPermissions.php b/src/permission/DefaultPermissions.php index 9913ac76925..56797fddd51 100644 --- a/src/permission/DefaultPermissions.php +++ b/src/permission/DefaultPermissions.php @@ -122,6 +122,7 @@ public static function registerCorePermissions() : void{ Names::COMMAND_TRANSFERSERVER, Names::COMMAND_UNBAN_IP, Names::COMMAND_UNBAN_PLAYER, + Names::COMMAND_WEATHER, Names::COMMAND_WHITELIST_ADD, Names::COMMAND_WHITELIST_DISABLE, Names::COMMAND_WHITELIST_ENABLE, diff --git a/src/world/World.php b/src/world/World.php index 3a5014413c4..99f8f637b2c 100644 --- a/src/world/World.php +++ b/src/world/World.php @@ -41,6 +41,7 @@ use pocketmine\data\SavedDataLoadingException; use pocketmine\entity\Entity; use pocketmine\entity\EntityFactory; +use pocketmine\entity\LightningBolt; use pocketmine\entity\Location; use pocketmine\entity\NeverSavedWithChunkEntity; use pocketmine\entity\object\ExperienceOrb; @@ -53,6 +54,7 @@ use pocketmine\event\world\ChunkPopulateEvent; use pocketmine\event\world\ChunkUnloadEvent; use pocketmine\event\world\SpawnChangeEvent; +use pocketmine\event\world\WeatherChangeEvent; use pocketmine\event\world\WorldDifficultyChangeEvent; use pocketmine\event\world\WorldDisplayNameChangeEvent; use pocketmine\event\world\WorldParticleEvent; @@ -81,6 +83,7 @@ use pocketmine\scheduler\AsyncPool; use pocketmine\Server; use pocketmine\ServerConfigGroup; +use pocketmine\ServerProperties; use pocketmine\utils\AssumptionFailedError; use pocketmine\utils\Limits; use pocketmine\utils\ReversePriorityQueue; @@ -115,6 +118,7 @@ use function array_keys; use function array_map; use function array_merge; +use function array_rand; use function array_sum; use function array_values; use function assert; @@ -225,6 +229,12 @@ class World implements ChunkManager{ private int $minY; private int $maxY; + private int $weatherDuration = 0; + private int $weatherTick = 0; + private bool $weatherEnabled = true; + private float $rainLevel = 0.0; + private float $lightningLevel = 0.0; + /** * @var ChunkTicker[][] chunkHash => [spl_object_id => ChunkTicker] * @phpstan-var array> @@ -517,6 +527,8 @@ public function __construct( $this->worldId ); + $this->weatherEnabled = $this->getServer()->getConfigGroup()->getConfigBool(ServerProperties::WEATHER_ENABLED, true); + $this->chunkPopulationRequestQueue = new \SplQueue(); $this->addOnUnloadCallback(function() : void{ $this->logger->debug("Cancelling unfulfilled generation requests"); @@ -588,6 +600,124 @@ private function initRandomTickBlocksFromConfig(ServerConfigGroup $cfg) : void{ } } + public function setWeather(float $rainLevel, float $lightningLevel, int $duration = 6000) : void{ + $ev = new WeatherChangeEvent($this, $this->rainLevel, $this->lightningLevel, $rainLevel, $lightningLevel); + $ev->call(); + if($ev->isCancelled()){ + return; + } + + $this->rainLevel = $ev->getNewRainLevel(); + $this->lightningLevel = $ev->getNewLightningLevel(); + $this->weatherDuration = $duration; + $this->weatherTick = 0; + + $this->sendWeather(); + } + + private function tickWeather() : void{ + if(!$this->weatherEnabled){ + return; + } + + $this->rainLevel = max(0.0, min(1.0, $this->rainLevel + (mt_rand(-5, 5) / 100))); + $this->lightningLevel = max(0.0, min(1.0, $this->lightningLevel + (mt_rand(-3, 3) / 100))); + + $this->weatherTick++; + + if($this->weatherTick >= $this->weatherDuration && $this->weatherDuration > 0){ + $this->autoChangeWeather(); + } + + if($this->lightningLevel > 0.8 && mt_rand(0, 200) === 0){ + $players = $this->getPlayers(); + if(count($players) > 0){ + $target = $players[array_rand($players)]; + $spawn = $target->getPosition(); + $x = (int) $spawn->x + mt_rand(-30, 30); + $z = (int) $spawn->z + mt_rand(-30, 30); + $y = $this->getHighestBlockAt($x, $z) ?? max(64, (int) $spawn->y); + + $location = Location::fromObject(new Vector3($x, $y, $z), $this); + (new LightningBolt($location))->spawnToAll(); + } + } + } + + private function autoChangeWeather() : void{ + $roll = mt_rand(1, 100); + + if($roll <= 70){ + $this->setWeather(0.0, 0.0, mt_rand(6000, 12000)); + }elseif($roll <= 90){ + $this->setWeather(1.0, 0.0, mt_rand(4000, 9000)); + }else{ + $this->setWeather(1.0, 1.0, mt_rand(2000, 6000)); + } + } + + private function sendWeather(Player ...$targets) : void{ + if(count($targets) === 0){ + $targets = $this->players; + } + + foreach($targets as $player){ + $player->getNetworkSession()->syncWorldWeather($this); + } + } + + public function getRainLevel() : float{ + return $this->rainLevel; + } + + public function setRainLevel(float $level) : void { + if($level < 0.0 || $level > 1.0){ + throw new \InvalidArgumentException("Rain level must be between 0.0 and 1.0, $level given"); + } + $this->rainLevel = $level; + } + + public function getLightningLevel() : float{ + return $this->lightningLevel; + } + + public function setLightningLevel(float $level) : void { + if($level < 0.0 || $level > 1.0){ + throw new \InvalidArgumentException("Thunder level must be between 0.0 and 1.0, $level given"); + } + $this->lightningLevel = $level; + } + + public function isWeatherEnabled() : bool{ + return $this->weatherEnabled; + } + + public function isRainingAt(int $x, int $y, int $z) : bool{ + if($this->getRainLevel() <= 0.0){ + return false; + } + + if(!$this->isChunkLoaded($x >> Chunk::COORD_BIT_SIZE, $z >> Chunk::COORD_BIT_SIZE)){ + return false; + } + + $highestY = $this->getHighestBlockAt($x, $z); + + for($currentY = $highestY; $currentY > $y; $currentY--){ + $block = $this->getBlockAt($x, $currentY, $z); + + if($block->getLightFilter() >= 15 || $block->isSolid()){ + return false; + } + } + + return true; + } + + public function isRaining(Vector3 $pos) : bool{ + return $this->isRainingAt($pos->getFloorX(), $pos->getFloorY(), $pos->getFloorZ()); + } + public function getTickRateTime() : float{ return $this->tickRateTime; } @@ -945,6 +1075,8 @@ protected function actuallyDoTick(int $currentTick) : void{ $this->sendTimeTicker = 0; } + $this->tickWeather(); + $this->unloadChunks(); if(++$this->providerGarbageCollectionTicker >= 6000){ $this->provider->doGarbageCollection(); @@ -1435,6 +1567,8 @@ public function save(bool $force = false) : bool{ $timings->startTiming(); $this->provider->getWorldData()->setTime($this->time); + $this->provider->getWorldData()->setRainLevel($this->rainLevel); + $this->provider->getWorldData()->setLightningLevel($this->lightningLevel); $this->saveChunks(); $this->provider->getWorldData()->save(); @@ -1740,7 +1874,9 @@ public function getSunAngleDegrees() : float{ public function computeSkyLightReduction() : int{ $percentage = max(0, min(1, -(cos($this->getSunAngleRadians()) * 2 - 0.5))); - //TODO: check rain and thunder level + $weatherDarkness = max($this->rainLevel, $this->lightningLevel) * 0.8; + + $percentage = min(1, $percentage + $weatherDarkness); return (int) ($percentage * 11); } diff --git a/src/world/sound/ThunderSound.php b/src/world/sound/ThunderSound.php new file mode 100644 index 00000000000..292f4e635ca --- /dev/null +++ b/src/world/sound/ThunderSound.php @@ -0,0 +1,35 @@ +